removed model picker

This commit is contained in:
Davit
2026-04-24 17:49:52 +04:00
parent 3dfa8b88a5
commit 2d732c282c
16 changed files with 15 additions and 884 deletions
-24
View File
@@ -111,7 +111,6 @@ export interface OpenclawSkillsLimits {
export interface OpenclawSubagentsSection {
allowAgents?: string[];
model?: string | { primary?: string; fallbacks?: string[] };
thinking?: string;
requireAgentId?: boolean;
}
@@ -172,26 +171,6 @@ export interface AgentBudgetResponse {
export type AgentBudgetPatch = Partial<Record<AgentBudgetKey, number | null>>;
// ── Agent model config ──
export interface AgentModelOption {
key: string;
alias: string | null;
}
export interface AgentModelConfigResponse {
agentId: string;
known: boolean;
override: string | null;
systemDefault: string | null;
effective: string | null;
available: AgentModelOption[];
}
export interface AgentModelConfigPatch {
model: string | null;
}
// ── Agent skills (per-agent allowlist) ──
export interface AgentSkillSummary {
@@ -221,7 +200,6 @@ export type AgentSubagentsThinking = 'minimal' | 'low' | 'medium' | 'high' | 'in
export interface AgentSubagentsConfig {
allowAgents: string[] | null;
model: string | null;
thinking: string | null;
requireAgentId: boolean | null;
}
@@ -231,12 +209,10 @@ export interface AgentSubagentsResponse {
known: boolean;
config: AgentSubagentsConfig;
availableAgents: { id: string; name: string | null }[];
availableModels: AgentModelOption[];
}
export interface AgentSubagentsPatch {
allowAgents?: string[] | null;
model?: string | null;
thinking?: string | null;
requireAgentId?: boolean | null;
}
-31
View File
@@ -404,35 +404,6 @@ const updateBudget: RequestHandler = async (req, res, next) => {
}
};
const getModelConfig: RequestHandler = async (req, res, next) => {
try {
const agentRepo = AppDataSource.getRepository(Agent);
const agent = await agentRepo.findOneBy({ _id: Number(req.params.id) });
if (!agent?.openclawAgentId) {
return res.status(404).json({ error: 'Agent not found' });
}
return res.json(ocService.getAgentModelConfig(agent.openclawAgentId));
} catch (error) {
return next(error);
}
};
const updateModelConfig: RequestHandler = async (req, res, next) => {
try {
const agentRepo = AppDataSource.getRepository(Agent);
const agent = await agentRepo.findOneBy({ _id: Number(req.params.id) });
if (!agent?.openclawAgentId) {
return res.status(404).json({ error: 'Agent not found' });
}
const body = (req.body ?? {}) as { model?: string | null };
const result = ocService.setAgentModel(agent.openclawAgentId, body.model ?? null);
if (!result.ok) return res.status(400).json(result);
return res.json(result);
} catch (error) {
return next(error);
}
};
const getSkillsConfig: RequestHandler = async (req, res, next) => {
try {
const agentRepo = AppDataSource.getRepository(Agent);
@@ -522,8 +493,6 @@ export {
serveWorkspaceUpload,
getBudget,
updateBudget,
getModelConfig,
updateModelConfig,
getSkillsConfig,
updateSkillsConfig,
getSubagentsConfig,
-118
View File
@@ -397,72 +397,6 @@ paths:
$ref: '#/components/responses/422'
500:
$ref: '#/components/responses/500'
/agent/{id}/model-config:
get:
tags:
- agent
security:
- bearerAuth: []
operationId: getAgentModelConfig
summary: Get per-agent model override
description: >
Returns the agent's model override (if any), the effective model,
the system default, and the list of configured models available
to pick from.
parameters:
- $ref: '#/components/parameters/id'
responses:
200:
description: Model configuration for this agent
content:
application/json:
schema:
$ref: '#/components/schemas/agentModelConfigResponse'
404:
description: Agent not found
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
patch:
tags:
- agent
security:
- bearerAuth: []
operationId: updateAgentModelConfig
summary: Set or clear the per-agent model override
parameters:
- $ref: '#/components/parameters/id'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
model:
type: string
nullable: true
description: Model key to pin for this agent. Send null to inherit default.
responses:
200:
description: Updated model configuration
content:
application/json:
schema:
$ref: '#/components/schemas/agentModelConfigMutationResponse'
400:
description: Model update failed
content:
application/json:
schema:
$ref: '#/components/schemas/agentModelConfigMutationResponse'
401:
$ref: '#/components/responses/401'
422:
$ref: '#/components/responses/422'
500:
$ref: '#/components/responses/500'
/agent/{id}/skills:
get:
tags:
@@ -776,48 +710,6 @@ components:
config:
$ref: '#/components/schemas/agentBudgetResponse'
#
agentModelOption:
type: object
properties:
key:
type: string
description: Model key (what gets written to config).
alias:
type: string
nullable: true
#
agentModelConfigResponse:
type: object
properties:
agentId:
type: string
known:
type: boolean
override:
type: string
nullable: true
systemDefault:
type: string
nullable: true
effective:
type: string
nullable: true
available:
type: array
items:
$ref: '#/components/schemas/agentModelOption'
#
agentModelConfigMutationResponse:
type: object
properties:
ok:
type: boolean
error:
type: string
nullable: true
config:
$ref: '#/components/schemas/agentModelConfigResponse'
#
agentSkillSummary:
type: object
properties:
@@ -874,9 +766,6 @@ components:
items:
type: string
description: Explicit child-agent allowlist, or null when unrestricted.
model:
type: string
nullable: true
thinking:
type: string
nullable: true
@@ -908,10 +797,6 @@ components:
items:
$ref: '#/components/schemas/agentRef'
description: Configured agents (excluding self) that can appear in allowAgents.
availableModels:
type: array
items:
$ref: '#/components/schemas/agentModelOption'
#
agentSubagentsPatch:
type: object
@@ -922,9 +807,6 @@ components:
nullable: true
items:
type: string
model:
type: string
nullable: true
thinking:
type: string
nullable: true
-5
View File
@@ -31,11 +31,6 @@ router
.get(auth, validate.id, controller.getSubagentsConfig)
.patch(auth, validate.subagentsPatch, controller.updateSubagentsConfig);
router
.route('/agent/:id(\\d+)/model-config')
.get(auth, validate.id, controller.getModelConfig)
.patch(auth, validate.modelPatch, controller.updateModelConfig);
router
.route('/agent/:id(\\d+)/budget')
.get(auth, validate.id, controller.getBudget)
+1 -18
View File
@@ -89,7 +89,7 @@ export default {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('Body must be an object.');
}
const allowed = ['allowAgents', 'model', 'thinking', 'requireAgentId'];
const allowed = ['allowAgents', 'thinking', 'requireAgentId'];
const unknownKey = Object.keys(value).find((k) => !allowed.includes(k));
if (unknownKey) {
throw new Error(`Unknown field: ${unknownKey}`);
@@ -103,9 +103,6 @@ export default {
throw new Error('Every allowed agent id must be a string.');
}
}
if ('model' in v && v.model !== null && typeof v.model !== 'string') {
throw new Error('"model" must be a string or null.');
}
if ('thinking' in v && v.thinking !== null && typeof v.thinking !== 'string') {
throw new Error('"thinking" must be a string or null.');
}
@@ -120,20 +117,6 @@ export default {
}),
]),
modelPatch: validate([
param('id').isInt().withMessage('Incorrect request url'),
body('model').custom((value) => {
if (value === null) return true;
if (typeof value !== 'string') {
throw new Error('"model" must be a string or null.');
}
if (value.length > 200) {
throw new Error('"model" is too long.');
}
return true;
}),
]),
budgetPatch: validate([
param('id').isInt().withMessage('Incorrect request url'),
body().custom((value) => {
-110
View File
@@ -1,110 +0,0 @@
/* eslint-disable no-console */
import fs from 'fs';
import os from 'os';
import {
AgentModelConfigResponse,
AgentModelOption,
OpenclawAgentEntry,
OpenclawConfig,
} from '../../@types/openclaw';
import { ocExec } from '../openclawGateway';
import { openclawConfigPath } from './paths';
import { execErrText } from '../../utils/errors';
const CLI_OPTS = {
cwd: os.homedir(),
env: { ...process.env, NO_COLOR: '1' },
timeout: 15000,
};
function readConfig(): OpenclawConfig | null {
try {
return JSON.parse(fs.readFileSync(openclawConfigPath(), 'utf-8')) as OpenclawConfig;
} catch {
return null;
}
}
function findAgentIndex(config: OpenclawConfig | null, openclawAgentId: string): number {
const list = config?.agents?.list ?? [];
return list.findIndex((a) => a?.id === openclawAgentId);
}
function extractModel(entry: OpenclawAgentEntry | undefined): string | null {
if (!entry?.model) return null;
if (typeof entry.model === 'string') return entry.model;
if (entry.model && typeof entry.model === 'object') return entry.model.primary || null;
return null;
}
function listAvailableModels(config: OpenclawConfig | null): AgentModelOption[] {
const models = config?.agents?.defaults?.models ?? {};
return Object.entries(models).map(([key, val]) => ({
key,
alias: val && typeof val === 'object' && typeof val.alias === 'string' ? val.alias : null,
}));
}
export function getAgentModelConfig(openclawAgentId: string): AgentModelConfigResponse {
const config = readConfig();
const agentIndex = findAgentIndex(config, openclawAgentId);
const list = config?.agents?.list ?? [];
const entry = agentIndex >= 0 ? list[agentIndex] : undefined;
const override = extractModel(entry);
const systemDefault = config?.agents?.defaults?.model?.primary ?? null;
return {
agentId: openclawAgentId,
known: agentIndex >= 0,
override,
systemDefault,
effective: override ?? systemDefault,
available: listAvailableModels(config),
};
}
export interface SetAgentModelResult {
ok: boolean;
error?: string;
config?: AgentModelConfigResponse;
}
export function setAgentModel(
openclawAgentId: string,
modelKey: string | null
): SetAgentModelResult {
const config = readConfig();
const agentIndex = findAgentIndex(config, openclawAgentId);
if (agentIndex < 0) {
return { ok: false, error: `Agent "${openclawAgentId}" not found in openclaw config.` };
}
const path = `agents.list[${agentIndex}].model`;
try {
if (modelKey === null || modelKey === '') {
try {
ocExec(['config', 'unset', path], CLI_OPTS);
} catch (err) {
const stderr = execErrText(err);
if (!/not found|does not exist/i.test(stderr)) throw err;
}
} else {
const available = listAvailableModels(config).map((m) => m.key);
if (!available.includes(modelKey)) {
return {
ok: false,
error: `Model "${modelKey}" is not configured. Configure it via \`openclaw models\` first.`,
};
}
ocExec(['config', 'set', path, JSON.stringify(modelKey), '--strict-json'], CLI_OPTS);
}
} catch (err) {
const stderr = execErrText(err);
console.error(`[agent-model] failed to set ${path}:`, stderr);
return { ok: false, error: stderr || 'Failed to update agent model.' };
}
return { ok: true, config: getAgentModelConfig(openclawAgentId) };
}
+1 -31
View File
@@ -2,13 +2,11 @@
import fs from 'fs';
import os from 'os';
import {
AgentModelOption,
AgentSubagentsConfig,
AgentSubagentsPatch,
AgentSubagentsResponse,
OpenclawAgentEntry,
OpenclawConfig,
OpenclawSubagentsSection,
} from '../../@types/openclaw';
import { ocExec } from '../openclawGateway';
import { openclawConfigPath } from './paths';
@@ -35,19 +33,10 @@ function findAgentIndex(config: OpenclawConfig | null, openclawAgentId: string):
return list.findIndex((a) => a?.id === openclawAgentId);
}
function extractModel(section: OpenclawSubagentsSection | undefined): string | null {
const m = section?.model;
if (!m) return null;
if (typeof m === 'string') return m;
if (typeof m === 'object') return m.primary ?? null;
return null;
}
function normalizeConfig(entry: OpenclawAgentEntry | undefined): AgentSubagentsConfig {
const s = entry?.subagents;
return {
allowAgents: Array.isArray(s?.allowAgents) ? s!.allowAgents.map(String) : null,
model: extractModel(s),
thinking: typeof s?.thinking === 'string' ? s!.thinking : null,
requireAgentId: typeof s?.requireAgentId === 'boolean' ? s!.requireAgentId : null,
};
@@ -63,14 +52,6 @@ function availableAgents(config: OpenclawConfig | null, selfId: string) {
}));
}
function availableModels(config: OpenclawConfig | null): AgentModelOption[] {
const models = config?.agents?.defaults?.models ?? {};
return Object.entries(models).map(([key, val]) => ({
key,
alias: val && typeof val === 'object' && typeof val.alias === 'string' ? val.alias : null,
}));
}
export function getAgentSubagentsConfig(openclawAgentId: string): AgentSubagentsResponse {
const config = readConfig();
const agentIndex = findAgentIndex(config, openclawAgentId);
@@ -81,7 +62,6 @@ export function getAgentSubagentsConfig(openclawAgentId: string): AgentSubagents
known: agentIndex >= 0,
config: normalizeConfig(entry),
availableAgents: availableAgents(config, openclawAgentId),
availableModels: availableModels(config),
};
}
@@ -103,11 +83,6 @@ const HANDLERS: Record<keyof AgentSubagentsPatch, PatchHandler> = {
const normalized = Array.from(new Set(v.map((x) => String(x).trim()).filter(Boolean)));
return { op: 'set', value: JSON.stringify(normalized) };
},
model: (_b, v) => {
if (v === null || v === '') return { op: 'unset' };
if (typeof v !== 'string') return { op: 'error', error: '"model" must be a string or null.' };
return { op: 'set', value: JSON.stringify(v) };
},
thinking: (_b, v) => {
if (v === null || v === '' || v === 'inherit') return { op: 'unset' };
if (typeof v !== 'string' || !ALLOWED_THINKING.has(v)) {
@@ -136,12 +111,7 @@ export function setAgentSubagents(
return { ok: false, error: `Agent "${openclawAgentId}" not found in openclaw config.` };
}
const keys: (keyof AgentSubagentsPatch)[] = [
'allowAgents',
'model',
'thinking',
'requireAgentId',
];
const keys: (keyof AgentSubagentsPatch)[] = ['allowAgents', 'thinking', 'requireAgentId'];
const pending = keys.filter((k) => k in patch);
-1
View File
@@ -31,7 +31,6 @@ export {
} from './workspace';
export { getAgentModel, getAgentModelsForOpenclawIds } from './config';
export { getAgentBudget, setAgentBudget, BUDGET_FIELDS } from './budget';
export { getAgentModelConfig, setAgentModel } from './agentModel';
export { getAgentSkillsConfig, setAgentSkills } from './agentSkills';
export { getAgentSubagentsConfig, setAgentSubagents } from './agentSubagents';
export { listPlugins, togglePlugin } from './plugins';
-43
View File
@@ -78,26 +78,6 @@ export interface AgentBudgetMutationResponse {
budget?: AgentBudgetResponse;
}
export interface AgentModelOption {
key: string;
alias: string | null;
}
export interface AgentModelConfigResponse {
agentId: string;
known: boolean;
override: string | null;
systemDefault: string | null;
effective: string | null;
available: AgentModelOption[];
}
export interface AgentModelConfigMutationResponse {
ok: boolean;
error?: string;
config?: AgentModelConfigResponse;
}
export interface AgentSkillSummary {
name: string;
description: string;
@@ -123,7 +103,6 @@ export interface AgentSkillsMutationResponse {
export interface AgentSubagentsConfig {
allowAgents: string[] | null;
model: string | null;
thinking: string | null;
requireAgentId: boolean | null;
}
@@ -133,12 +112,10 @@ export interface AgentSubagentsResponse {
known: boolean;
config: AgentSubagentsConfig;
availableAgents: { id: string; name: string | null }[];
availableModels: AgentModelOption[];
}
export interface AgentSubagentsPatch {
allowAgents?: string[] | null;
model?: string | null;
thinking?: string | null;
requireAgentId?: boolean | null;
}
@@ -266,24 +243,6 @@ export const agentsApi = baseApi.injectEndpoints({
}),
invalidatesTags: (_res, _err, { agentId }) => [{ type: 'AgentBudget', id: agentId }],
}),
getAgentModelConfig: build.query<AgentModelConfigResponse, string>({
query: (agentId) => `/agent/${agentId}/model-config`,
providesTags: (_res, _err, agentId) => [{ type: 'AgentModelConfig', id: agentId }],
}),
updateAgentModelConfig: build.mutation<
AgentModelConfigMutationResponse,
{ agentId: string; model: string | null }
>({
query: ({ agentId, model }) => ({
url: `/agent/${agentId}/model-config`,
method: 'PATCH',
body: { model },
}),
invalidatesTags: (_res, _err, { agentId }) => [
{ type: 'AgentModelConfig', id: agentId },
'Agent',
],
}),
getAgentSkills: build.query<AgentSkillsResponse, string>({
query: (agentId) => `/agent/${agentId}/skills`,
providesTags: (_res, _err, agentId) => [{ type: 'AgentSkills', id: agentId }],
@@ -331,8 +290,6 @@ export const {
usePatchSessionSettingsMutation,
useGetAgentBudgetQuery,
useUpdateAgentBudgetMutation,
useGetAgentModelConfigQuery,
useUpdateAgentModelConfigMutation,
useGetAgentSkillsQuery,
useUpdateAgentSkillsMutation,
useGetAgentSubagentsQuery,
@@ -1,2 +0,0 @@
export { default as AgentModelConfig } from './ui/AgentModelConfig';
export { default as AgentModelPicker } from './ui/AgentModelPicker';
@@ -1,230 +0,0 @@
import { useState } from 'react';
import {
Alert,
Box,
Button,
Chip,
CircularProgress,
FormControl,
InputLabel,
MenuItem,
Select,
Stack,
Typography,
} from '@mui/material';
import {
useGetAgentModelConfigQuery,
useUpdateAgentModelConfigMutation,
} from '../../../../entities/agent';
interface AgentModelConfigProps {
agentId: string;
}
const INHERIT_VALUE = '__inherit__';
export default function AgentModelConfig({ agentId }: AgentModelConfigProps) {
const { data, isLoading, isError, refetch } = useGetAgentModelConfigQuery(agentId, {
skip: !agentId,
});
const [update, { isLoading: saving, error: saveError }] = useUpdateAgentModelConfigMutation();
const [selection, setSelection] = useState<string>(INHERIT_VALUE);
const [prevData, setPrevData] = useState(data);
if (data !== prevData) {
setPrevData(data);
if (data) setSelection(data.override ?? INHERIT_VALUE);
}
const dirty = data ? selection !== (data.override ?? INHERIT_VALUE) : false;
async function handleSave() {
try {
await update({
agentId,
model: selection === INHERIT_VALUE ? null : selection,
}).unwrap();
} catch (err) {
console.error('Save agent model failed:', err);
}
}
function handleReset() {
if (!data) return;
setSelection(data.override ?? INHERIT_VALUE);
}
if (isLoading && !data) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
<CircularProgress size={20} />
</Box>
);
}
if (isError || !data) {
return (
<Alert
severity="error"
action={
<Button size="small" onClick={() => refetch()}>
Retry
</Button>
}
>
Could not load agent model configuration.
</Alert>
);
}
const rpcError =
saveError && typeof saveError === 'object' && 'data' in saveError
? ((saveError as { data?: { error?: string } }).data?.error ?? 'Failed to save model.')
: null;
const systemDefaultLabel = data.systemDefault ?? 'unset';
return (
<Box sx={{ width: '100%', minWidth: 0 }}>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={1}
sx={{ mb: 1 }}
>
<Box>
<Typography variant="subtitle2" fontWeight={700}>
Model
</Typography>
<Typography variant="caption" color="text.secondary">
Override the primary model for this agent. Leave as "Inherit default" to use the system
default.
</Typography>
</Box>
{!data.known && (
<Chip
size="small"
color="warning"
variant="outlined"
label="Agent missing from openclaw config"
/>
)}
</Stack>
{rpcError && (
<Alert severity="error" sx={{ mb: 1 }}>
{rpcError}
</Alert>
)}
<Box
sx={{
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
p: 1.5,
bgcolor: 'background.paper',
}}
>
<Stack spacing={1.25}>
<FormControl fullWidth size="small" disabled={!data.known}>
<InputLabel id="agent-model-select-label">Primary model</InputLabel>
<Select
labelId="agent-model-select-label"
label="Primary model"
value={selection}
onChange={(e) => setSelection(String(e.target.value))}
>
<MenuItem value={INHERIT_VALUE}>
<Box component="span">
Inherit default{' '}
<Box
component="span"
sx={{
color: 'text.secondary',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.72rem',
}}
>
({systemDefaultLabel})
</Box>
</Box>
</MenuItem>
{data.available.length === 0 && (
<MenuItem value="" disabled>
No models configured add via <code>openclaw models</code>
</MenuItem>
)}
{data.available.map((m) => (
<MenuItem key={m.key} value={m.key}>
<Box
component="span"
sx={{
display: 'inline-flex',
alignItems: 'baseline',
gap: 1,
minWidth: 0,
}}
>
<Box
component="span"
sx={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.8rem',
}}
>
{m.key}
</Box>
{m.alias && (
<Box
component="span"
sx={{
color: 'text.secondary',
fontSize: '0.72rem',
}}
>
alias {m.alias}
</Box>
)}
</Box>
</MenuItem>
))}
</Select>
</FormControl>
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.7rem',
}}
>
<Box component="span" sx={{ color: 'text.primary', fontWeight: 600 }}>
effective {data.effective ?? '—'}
</Box>
<Box component="span" sx={{ opacity: 0.55, mx: 0.75 }}>
·
</Box>
system default {systemDefaultLabel}
</Typography>
</Stack>
</Box>
<Stack direction="row" justifyContent="flex-end" spacing={1} sx={{ mt: 1.5 }}>
<Button size="small" onClick={handleReset} disabled={!dirty || saving}>
Reset
</Button>
<Button
size="small"
variant="contained"
onClick={handleSave}
disabled={!dirty || saving || !data.known}
>
{saving ? 'Saving…' : 'Save'}
</Button>
</Stack>
</Box>
);
}
@@ -1,197 +0,0 @@
import { useState } from 'react';
import {
Box,
ButtonBase,
CircularProgress,
Divider,
Menu,
MenuItem,
Tooltip,
Typography,
} from '@mui/material';
import { ExpandMore, Check } from '@mui/icons-material';
import {
useGetAgentModelConfigQuery,
useUpdateAgentModelConfigMutation,
} from '../../../../entities/agent';
interface AgentModelPickerProps {
agentId: string;
}
function shortenModel(key: string | null): string {
if (!key) return 'no model';
const slash = key.indexOf('/');
return slash >= 0 ? key.slice(slash + 1) : key;
}
export default function AgentModelPicker({ agentId }: AgentModelPickerProps) {
const { data, isLoading } = useGetAgentModelConfigQuery(agentId, { skip: !agentId });
const [update, { isLoading: saving }] = useUpdateAgentModelConfigMutation();
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
if (isLoading && !data) {
return <CircularProgress size={12} sx={{ ml: 0.25 }} />;
}
if (!data) return null;
const effective = data.effective;
const override = data.override;
const label = shortenModel(effective);
async function handlePick(model: string | null) {
setAnchor(null);
if (!data?.known) return;
if ((override ?? null) === (model ?? null)) return;
try {
await update({ agentId, model }).unwrap();
} catch (err) {
console.error('Agent model update failed:', err);
}
}
const canEdit = data.known && !saving;
return (
<>
<Tooltip
title={
data.known
? effective
? `Model: ${effective}${override ? '' : ' (inherited default)'}`
: 'Pick a model'
: 'Agent not in openclaw config'
}
placement="bottom-start"
>
<Box
component="span"
sx={{ display: 'inline-flex', alignItems: 'center', minWidth: 0, maxWidth: '100%' }}
>
<ButtonBase
onClick={(e) => canEdit && setAnchor(e.currentTarget)}
disabled={!canEdit}
focusRipple
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.35,
px: 0.6,
py: 0.15,
ml: -0.6,
borderRadius: 0.75,
maxWidth: '100%',
color: 'text.secondary',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.7rem',
lineHeight: 1.3,
opacity: data.known ? 1 : 0.6,
cursor: canEdit ? 'pointer' : 'default',
'&:hover': canEdit ? { bgcolor: 'action.hover', color: 'text.primary' } : undefined,
'&:focus-visible': {
outline: '2px solid',
outlineColor: 'primary.main',
outlineOffset: 1,
},
}}
>
<Box
component="span"
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
minWidth: 0,
}}
>
{label}
</Box>
{!override && effective && (
<Box component="span" sx={{ opacity: 0.55, fontSize: '0.65rem' }}>
(default)
</Box>
)}
{saving ? (
<CircularProgress size={10} sx={{ ml: 0.25 }} />
) : (
<ExpandMore sx={{ fontSize: 14, opacity: canEdit ? 0.7 : 0.3 }} />
)}
</ButtonBase>
</Box>
</Tooltip>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={() => setAnchor(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
slotProps={{ paper: { sx: { minWidth: 260, maxWidth: 360 } } }}
>
<MenuItem onClick={() => handlePick(null)}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, minWidth: 0, flex: 1 }}>
<Box sx={{ width: 16, flexShrink: 0 }}>
{override === null && <Check sx={{ fontSize: 16 }} />}
</Box>
<Box sx={{ minWidth: 0 }}>
<Typography variant="body2">Inherit default</Typography>
{data.systemDefault && (
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.68rem',
}}
>
{data.systemDefault}
</Typography>
)}
</Box>
</Box>
</MenuItem>
<Divider />
{data.available.length === 0 && (
<MenuItem disabled>
<Typography variant="caption" color="text.secondary">
No models configured. Run <code>openclaw models</code>.
</Typography>
</MenuItem>
)}
{data.available.map((m) => {
const isActive = override === m.key;
return (
<MenuItem key={m.key} selected={isActive} onClick={() => handlePick(m.key)}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, minWidth: 0, flex: 1 }}>
<Box sx={{ width: 16, flexShrink: 0 }}>
{isActive && <Check sx={{ fontSize: 16 }} />}
</Box>
<Box sx={{ minWidth: 0 }}>
<Typography
variant="body2"
sx={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.82rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{m.key}
</Typography>
{m.alias && (
<Typography
variant="caption"
sx={{ color: 'text.secondary', fontSize: '0.68rem' }}
>
alias {m.alias}
</Typography>
)}
</Box>
</Box>
</MenuItem>
);
})}
</Menu>
</>
);
}
@@ -26,10 +26,6 @@ import {
type AgentSubagentsPatch,
} from '../../../../entities/agent';
interface AgentSubagentsProps {
agentId: string;
}
const THINKING_OPTIONS: { value: string; label: string }[] = [
{ value: 'inherit', label: 'Inherit' },
{ value: 'minimal', label: 'Minimal' },
@@ -38,7 +34,9 @@ const THINKING_OPTIONS: { value: string; label: string }[] = [
{ value: 'high', label: 'High' },
];
const INHERIT_MODEL = '__inherit__';
interface AgentSubagentsProps {
agentId: string;
}
function sameSet(a: string[], b: string[]): boolean {
if (a.length !== b.length) return false;
@@ -55,7 +53,6 @@ export default function AgentSubagents({ agentId }: AgentSubagentsProps) {
const [restrict, setRestrict] = useState(false);
const [allowed, setAllowed] = useState<Set<string>>(new Set());
const [model, setModel] = useState<string>(INHERIT_MODEL);
const [thinking, setThinking] = useState<string>('inherit');
const [requireAgentId, setRequireAgentId] = useState<boolean | null>(null);
const [query, setQuery] = useState('');
@@ -71,7 +68,6 @@ export default function AgentSubagents({ agentId }: AgentSubagentsProps) {
setRestrict(true);
setAllowed(new Set(data.config.allowAgents));
}
setModel(data.config.model ?? INHERIT_MODEL);
setThinking(data.config.thinking ?? 'inherit');
setRequireAgentId(data.config.requireAgentId);
}
@@ -98,10 +94,6 @@ export default function AgentSubagents({ agentId }: AgentSubagentsProps) {
: nextAllow === null || !sameSet(currentAllow, nextAllow);
if (changedAllow) result.allowAgents = nextAllow;
const currentModel = data.config.model;
const nextModel = model === INHERIT_MODEL ? null : model;
if ((currentModel ?? null) !== (nextModel ?? null)) result.model = nextModel;
const currentThinking = data.config.thinking;
const nextThinking = thinking === 'inherit' ? null : thinking;
if ((currentThinking ?? null) !== (nextThinking ?? null)) result.thinking = nextThinking;
@@ -111,7 +103,7 @@ export default function AgentSubagents({ agentId }: AgentSubagentsProps) {
result.requireAgentId = requireAgentId;
return result;
}, [data, restrict, allowed, model, thinking, requireAgentId]);
}, [data, restrict, allowed, thinking, requireAgentId]);
const dirty = Object.keys(patch).length > 0;
@@ -149,7 +141,6 @@ export default function AgentSubagents({ agentId }: AgentSubagentsProps) {
setRestrict(true);
setAllowed(new Set(data.config.allowAgents));
}
setModel(data.config.model ?? INHERIT_MODEL);
setThinking(data.config.thinking ?? 'inherit');
setRequireAgentId(data.config.requireAgentId);
}
@@ -368,44 +359,6 @@ export default function AgentSubagents({ agentId }: AgentSubagentsProps) {
}}
>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.25} sx={{ mb: 0.5 }}>
<FormControl fullWidth size="small" disabled={!data.known || saving}>
<InputLabel id="subagent-model-label">Default model for subagents</InputLabel>
<Select
labelId="subagent-model-label"
label="Default model for subagents"
value={model}
onChange={(e) => setModel(String(e.target.value))}
>
<MenuItem value={INHERIT_MODEL}>Inherit</MenuItem>
{data.availableModels.length === 0 && (
<MenuItem value="" disabled>
No models configured
</MenuItem>
)}
{data.availableModels.map((m) => (
<MenuItem key={m.key} value={m.key}>
<Box
component="span"
sx={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: '0.8rem',
}}
>
{m.key}
</Box>
{m.alias && (
<Box
component="span"
sx={{ color: 'text.secondary', fontSize: '0.72rem', ml: 1 }}
>
alias {m.alias}
</Box>
)}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl fullWidth size="small" disabled={!data.known || saving}>
<InputLabel id="subagent-thinking-label">Thinking level</InputLabel>
<Select
-2
View File
@@ -73,13 +73,11 @@ export const baseApi = createApi({
'Workspace',
'WorkspaceFile',
'SessionSettings',
'AgentModel',
'Channel',
'Cron',
'Plugin',
'Skill',
'AgentBudget',
'AgentModelConfig',
'AgentSkills',
'AgentSubagents',
],
+8 -20
View File
@@ -3,7 +3,6 @@ import { Box, TextField, IconButton, Typography, CircularProgress } from '@mui/m
import { Edit, Check, Settings, TuneOutlined } from '@mui/icons-material';
import { Link } from 'react-router';
import { useGetAgentQuery, useUpdateAgentMutation } from '../../../entities/agent';
import { AgentModelPicker } from '../../../features/agent/model-config';
interface ChatHeaderProps {
agentId: string;
@@ -99,30 +98,19 @@ export default function ChatHeader({
</>
) : (
<>
<Box
<Typography
variant="h6"
fontWeight={600}
sx={{
flex: 1,
minWidth: 0,
display: 'flex',
flexDirection: 'column',
gap: 0.1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
<Typography
variant="h6"
fontWeight={600}
sx={{
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
lineHeight: 1.2,
}}
>
{agent.name}
</Typography>
<AgentModelPicker agentId={agentId} />
</Box>
{agent.name}
</Typography>
<IconButton
size="small"
onClick={onToggleSessionSettings}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openclaw-client",
"version": "2.4.4",
"version": "2.4.5",
"description": "Web-based chat interface for OpenClaw AI agents",
"private": true,
"type": "module",