feat(omo): add OMO Slim (oh-my-opencode-slim) support

Implement full OMO Slim profile management to align with ai-toolbox:
- Backend: Slim service methods, DAO, Tauri commands, plugin conflict handling
- Frontend: types, API, query hooks, form integration with isSlim parameterization
- Slim variant: 6 agents (no categories), separate config file and plugin name
- Mutual exclusion: standard OMO and Slim cannot coexist as plugins
- i18n: zh/en/ja translations for all Slim agent descriptions
This commit is contained in:
Jason
2026-02-19 20:47:55 +08:00
parent 51476953ae
commit 8e219b5eb1
26 changed files with 1176 additions and 165 deletions
+26 -1
View File
@@ -61,7 +61,10 @@ import { UniversalProviderPanel } from "@/components/universal";
import { McpIcon } from "@/components/BrandIcons";
import { Button } from "@/components/ui/button";
import { SessionManagerPage } from "@/components/sessions/SessionManagerPage";
import { useDisableCurrentOmo } from "@/lib/query/omo";
import {
useDisableCurrentOmo,
useDisableCurrentOmoSlim,
} from "@/lib/query/omo";
import WorkspaceFilesPanel from "@/components/workspace/WorkspaceFilesPanel";
import EnvPanel from "@/components/openclaw/EnvPanel";
import ToolsPanel from "@/components/openclaw/ToolsPanel";
@@ -248,6 +251,23 @@ function App() {
});
};
const disableOmoSlimMutation = useDisableCurrentOmoSlim();
const handleDisableOmoSlim = () => {
disableOmoSlimMutation.mutate(undefined, {
onSuccess: () => {
toast.success(t("omo.disabled", { defaultValue: "OMO 已停用" }));
},
onError: (error: Error) => {
toast.error(
t("omo.disableFailed", {
defaultValue: "停用 OMO 失败: {{error}}",
error: extractErrorMessage(error),
}),
);
},
});
};
useEffect(() => {
let unsubscribe: (() => void) | undefined;
@@ -746,6 +766,11 @@ function App() {
onDisableOmo={
activeApp === "opencode" ? handleDisableOmo : undefined
}
onDisableOmoSlim={
activeApp === "opencode"
? handleDisableOmoSlim
: undefined
}
onDuplicate={handleDuplicateProvider}
onConfigureUsage={setUsageProvider}
onOpenWebsite={handleOpenWebsite}
+25 -8
View File
@@ -29,11 +29,14 @@ interface ProviderCardProps {
isInConfig?: boolean; // OpenCode: 是否已添加到 opencode.json
isOmo?: boolean;
isLastOmo?: boolean;
isOmoSlim?: boolean;
isLastOmoSlim?: boolean;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
onRemoveFromConfig?: (provider: Provider) => void;
onDisableOmo?: () => void;
onDisableOmoSlim?: () => void;
onConfigureUsage: (provider: Provider) => void;
onOpenWebsite: (url: string) => void;
onDuplicate: (provider: Provider) => void;
@@ -92,11 +95,14 @@ export function ProviderCard({
isInConfig = true,
isOmo = false,
isLastOmo = false,
isOmoSlim = false,
isLastOmoSlim = false,
onSwitch,
onEdit,
onDelete,
onRemoveFromConfig,
onDisableOmo,
onDisableOmoSlim,
onConfigureUsage,
onOpenWebsite,
onDuplicate,
@@ -117,6 +123,11 @@ export function ProviderCard({
}: ProviderCardProps) {
const { t } = useTranslation();
// OMO and OMO Slim share the same card behavior
const isAnyOmo = isOmo || isOmoSlim;
const isLastAnyOmo = isOmo ? isLastOmo : isLastOmoSlim;
const handleDisableAnyOmo = isOmoSlim ? onDisableOmoSlim : onDisableOmo;
const { data: health } = useProviderHealth(provider.id, appId);
const fallbackUrlText = t("provider.notConfigured", {
@@ -186,11 +197,11 @@ export function ProviderCard({
};
// 判断是否是"当前使用中"的供应商
// - OMO 供应商:使用 isCurrent
// - OMO/OMO Slim 供应商:使用 isCurrent
// - 累加模式应用(OpenCode 非 OMO / OpenClaw):不存在"当前"概念,始终返回 false
// - 故障转移模式:代理实际使用的供应商(activeProviderId
// - 普通模式:isCurrent
const isActiveProvider = isOmo
const isActiveProvider = isAnyOmo
? isCurrent
: appId === "opencode" || appId === "openclaw"
? false
@@ -198,10 +209,10 @@ export function ProviderCard({
? activeProviderId === provider.id
: isCurrent;
const shouldUseGreen = !isOmo && isProxyTakeover && isActiveProvider;
const shouldUseGreen = !isAnyOmo && isProxyTakeover && isActiveProvider;
const shouldUseBlue =
(isOmo && isActiveProvider) ||
(!isOmo && !isProxyTakeover && isActiveProvider);
(isAnyOmo && isActiveProvider) ||
(!isAnyOmo && !isProxyTakeover && isActiveProvider);
return (
<div
@@ -265,6 +276,12 @@ export function ProviderCard({
</span>
)}
{isOmoSlim && (
<span className="inline-flex items-center rounded-md bg-indigo-100 px-1.5 py-0.5 text-[10px] font-semibold text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300">
Slim
</span>
)}
{isProxyRunning && isInFailoverQueue && health && (
<ProviderHealthBadge
consecutiveFailures={health.consecutive_failures}
@@ -372,8 +389,8 @@ export function ProviderCard({
isInConfig={isInConfig}
isTesting={isTesting}
isProxyTakeover={isProxyTakeover}
isOmo={isOmo}
isLastOmo={isLastOmo}
isOmo={isAnyOmo}
isLastOmo={isLastAnyOmo}
onSwitch={() => onSwitch(provider)}
onEdit={() => onEdit(provider)}
onDuplicate={() => onDuplicate(provider)}
@@ -385,7 +402,7 @@ export function ProviderCard({
? () => onRemoveFromConfig(provider)
: undefined
}
onDisableOmo={onDisableOmo}
onDisableOmo={handleDisableAnyOmo}
onOpenTerminal={
onOpenTerminal ? () => onOpenTerminal(provider) : undefined
}
+34 -2
View File
@@ -33,7 +33,12 @@ import {
useAddToFailoverQueue,
useRemoveFromFailoverQueue,
} from "@/lib/query/failover";
import { useCurrentOmoProviderId, useOmoProviderCount } from "@/lib/query/omo";
import {
useCurrentOmoProviderId,
useOmoProviderCount,
useCurrentOmoSlimProviderId,
useOmoSlimProviderCount,
} from "@/lib/query/omo";
import { useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@@ -47,6 +52,7 @@ interface ProviderListProps {
onDelete: (provider: Provider) => void;
onRemoveFromConfig?: (provider: Provider) => void;
onDisableOmo?: () => void;
onDisableOmoSlim?: () => void;
onDuplicate: (provider: Provider) => void;
onConfigureUsage?: (provider: Provider) => void;
onOpenWebsite: (url: string) => void;
@@ -68,6 +74,7 @@ export function ProviderList({
onDelete,
onRemoveFromConfig,
onDisableOmo,
onDisableOmoSlim,
onDuplicate,
onConfigureUsage,
onOpenWebsite,
@@ -135,6 +142,8 @@ export function ProviderList({
const isOpenCode = appId === "opencode";
const { data: currentOmoId } = useCurrentOmoProviderId(isOpenCode);
const { data: omoProviderCount } = useOmoProviderCount(isOpenCode);
const { data: currentOmoSlimId } = useCurrentOmoSlimProviderId(isOpenCode);
const { data: omoSlimProviderCount } = useOmoSlimProviderCount(isOpenCode);
const getFailoverPriority = useCallback(
(providerId: string): number | undefined => {
@@ -239,13 +248,20 @@ export function ProviderList({
<div className="space-y-3">
{filteredProviders.map((provider) => {
const isOmo = provider.category === "omo";
const isOmoSlim = provider.category === "omo-slim";
const isOmoCurrent = isOmo && provider.id === (currentOmoId || "");
const isOmoSlimCurrent =
isOmoSlim && provider.id === (currentOmoSlimId || "");
return (
<SortableProviderCard
key={provider.id}
provider={provider}
isCurrent={
isOmo ? isOmoCurrent : provider.id === currentProviderId
isOmo
? isOmoCurrent
: isOmoSlim
? isOmoSlimCurrent
: provider.id === currentProviderId
}
appId={appId}
isInConfig={isProviderInConfig(provider.id)}
@@ -253,11 +269,18 @@ export function ProviderList({
isLastOmo={
isOmo && (omoProviderCount ?? 0) <= 1 && isOmoCurrent
}
isOmoSlim={isOmoSlim}
isLastOmoSlim={
isOmoSlim &&
(omoSlimProviderCount ?? 0) <= 1 &&
isOmoSlimCurrent
}
onSwitch={onSwitch}
onEdit={onEdit}
onDelete={onDelete}
onRemoveFromConfig={onRemoveFromConfig}
onDisableOmo={onDisableOmo}
onDisableOmoSlim={onDisableOmoSlim}
onDuplicate={onDuplicate}
onConfigureUsage={onConfigureUsage}
onOpenWebsite={onOpenWebsite}
@@ -371,11 +394,14 @@ interface SortableProviderCardProps {
isInConfig: boolean;
isOmo: boolean;
isLastOmo: boolean;
isOmoSlim: boolean;
isLastOmoSlim: boolean;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
onRemoveFromConfig?: (provider: Provider) => void;
onDisableOmo?: () => void;
onDisableOmoSlim?: () => void;
onDuplicate: (provider: Provider) => void;
onConfigureUsage?: (provider: Provider) => void;
onOpenWebsite: (url: string) => void;
@@ -401,11 +427,14 @@ function SortableProviderCard({
isInConfig,
isOmo,
isLastOmo,
isOmoSlim,
isLastOmoSlim,
onSwitch,
onEdit,
onDelete,
onRemoveFromConfig,
onDisableOmo,
onDisableOmoSlim,
onDuplicate,
onConfigureUsage,
onOpenWebsite,
@@ -445,11 +474,14 @@ function SortableProviderCard({
isInConfig={isInConfig}
isOmo={isOmo}
isLastOmo={isLastOmo}
isOmoSlim={isOmoSlim}
isLastOmoSlim={isLastOmoSlim}
onSwitch={onSwitch}
onEdit={onEdit}
onDelete={onDelete}
onRemoveFromConfig={onRemoveFromConfig}
onDisableOmo={onDisableOmo}
onDisableOmoSlim={onDisableOmoSlim}
onDuplicate={onDuplicate}
onConfigureUsage={
onConfigureUsage ? (item) => onConfigureUsage(item) : () => undefined
@@ -23,6 +23,7 @@ interface OmoCommonConfigEditorProps {
onGlobalConfigStateChange: (config: OmoGlobalConfig) => void;
globalConfigRef: React.RefObject<OmoGlobalConfigFieldsRef | null>;
fieldsKey: number;
isSlim?: boolean;
}
export function OmoCommonConfigEditor({
@@ -37,6 +38,7 @@ export function OmoCommonConfigEditor({
onGlobalConfigStateChange,
globalConfigRef,
fieldsKey,
isSlim = false,
}: OmoCommonConfigEditorProps) {
const { t } = useTranslation();
const [isDarkMode, setIsDarkMode] = useState(false);
@@ -153,6 +155,7 @@ export function OmoCommonConfigEditor({
ref={globalConfigRef as React.Ref<OmoGlobalConfigFieldsRef>}
onStateChange={onGlobalConfigStateChange}
hideSaveButtons
isSlim={isSlim}
/>
</div>
</FullScreenPanel>
@@ -41,10 +41,11 @@ import {
} from "lucide-react";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
import { useReadOmoLocalFile } from "@/lib/query/omo";
import { useReadOmoLocalFile, useReadOmoSlimLocalFile } from "@/lib/query/omo";
import {
OMO_BUILTIN_AGENTS,
OMO_BUILTIN_CATEGORIES,
OMO_SLIM_BUILTIN_AGENTS,
type OmoAgentDef,
type OmoCategoryDef,
} from "@/types/omo";
@@ -69,12 +70,13 @@ interface OmoFormFieldsProps {
>;
agents: Record<string, Record<string, unknown>>;
onAgentsChange: (agents: Record<string, Record<string, unknown>>) => void;
categories: Record<string, Record<string, unknown>>;
onCategoriesChange: (
categories?: Record<string, Record<string, unknown>>;
onCategoriesChange?: (
categories: Record<string, Record<string, unknown>>,
) => void;
otherFieldsStr: string;
onOtherFieldsStrChange: (value: string) => void;
isSlim?: boolean;
}
export type CustomModelItem = {
@@ -121,6 +123,9 @@ function DeferredKeyInput({
}
const BUILTIN_AGENT_KEYS = new Set(OMO_BUILTIN_AGENTS.map((a) => a.key));
const BUILTIN_AGENT_KEYS_SLIM = new Set(
OMO_SLIM_BUILTIN_AGENTS.map((a) => a.key),
);
const BUILTIN_CATEGORY_KEYS = new Set(OMO_BUILTIN_CATEGORIES.map((c) => c.key));
const EMPTY_VARIANT_VALUE = "__cc_switch_omo_variant_empty__";
@@ -303,13 +308,21 @@ export function OmoFormFields({
presetMetaMap: _presetMetaMap = {},
agents,
onAgentsChange,
categories,
categories = {},
onCategoriesChange,
otherFieldsStr,
onOtherFieldsStrChange,
isSlim = false,
}: OmoFormFieldsProps) {
const { t } = useTranslation();
const builtinAgentDefs = isSlim
? OMO_SLIM_BUILTIN_AGENTS
: OMO_BUILTIN_AGENTS;
const builtinAgentKeys = isSlim
? BUILTIN_AGENT_KEYS_SLIM
: BUILTIN_AGENT_KEYS;
const [mainAgentsOpen, setMainAgentsOpen] = useState(true);
const [subAgentsOpen, setSubAgentsOpen] = useState(true);
const [categoriesOpen, setCategoriesOpen] = useState(true);
@@ -329,7 +342,7 @@ export function OmoFormFields({
>({});
const [customAgents, setCustomAgents] = useState<CustomModelItem[]>(() =>
collectCustomModels(agents, BUILTIN_AGENT_KEYS),
collectCustomModels(agents, builtinAgentKeys),
);
const [customCategories, setCustomCategories] = useState<CustomModelItem[]>(
@@ -337,7 +350,7 @@ export function OmoFormFields({
);
useEffect(() => {
setCustomAgents(collectCustomModels(agents, BUILTIN_AGENT_KEYS));
setCustomAgents(collectCustomModels(agents, builtinAgentKeys));
}, [agents]);
useEffect(() => {
@@ -349,17 +362,18 @@ export function OmoFormFields({
onAgentsChange(
mergeCustomModelsIntoStore(
agents,
BUILTIN_AGENT_KEYS,
builtinAgentKeys,
customs,
modelVariantsMap,
),
);
},
[agents, onAgentsChange, modelVariantsMap],
[agents, onAgentsChange, modelVariantsMap, builtinAgentKeys],
);
const syncCustomCategories = useCallback(
(customs: CustomModelItem[]) => {
if (!onCategoriesChange) return;
onCategoriesChange(
mergeCustomModelsIntoStore(
categories,
@@ -709,7 +723,7 @@ export function OmoFormFields({
}
const updatedAgents = { ...agents };
for (const agentDef of OMO_BUILTIN_AGENTS) {
for (const agentDef of builtinAgentDefs) {
const recommendedValue = resolveRecommendedModel(agentDef.recommended);
if (recommendedValue && !updatedAgents[agentDef.key]?.model) {
updatedAgents[agentDef.key] = {
@@ -720,30 +734,35 @@ export function OmoFormFields({
}
onAgentsChange(updatedAgents);
const updatedCategories = { ...categories };
for (const catDef of OMO_BUILTIN_CATEGORIES) {
const recommendedValue = resolveRecommendedModel(catDef.recommended);
if (recommendedValue && !updatedCategories[catDef.key]?.model) {
updatedCategories[catDef.key] = {
...updatedCategories[catDef.key],
model: recommendedValue,
};
if (!isSlim && onCategoriesChange) {
const updatedCategories = { ...categories };
for (const catDef of OMO_BUILTIN_CATEGORIES) {
const recommendedValue = resolveRecommendedModel(catDef.recommended);
if (recommendedValue && !updatedCategories[catDef.key]?.model) {
updatedCategories[catDef.key] = {
...updatedCategories[catDef.key],
model: recommendedValue,
};
}
}
onCategoriesChange(updatedCategories);
}
onCategoriesChange(updatedCategories);
};
const configuredAgentCount = Object.keys(agents).length;
const configuredCategoryCount = Object.keys(categories).length;
const mainAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "main");
const subAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "sub");
const configuredCategoryCount = isSlim ? 0 : Object.keys(categories).length;
const mainAgents = builtinAgentDefs.filter((a) => a.group === "main");
const subAgents = builtinAgentDefs.filter((a) => a.group === "sub");
const readLocalFile = useReadOmoLocalFile();
const readSlimLocalFile = useReadOmoSlimLocalFile();
const [localFilePath, setLocalFilePath] = useState<string | null>(null);
const handleImportFromLocal = useCallback(async () => {
try {
const data = await readLocalFile.mutateAsync();
const data = isSlim
? await readSlimLocalFile.mutateAsync()
: await readLocalFile.mutateAsync();
const importedAgents =
(data.agents as Record<string, Record<string, unknown>> | undefined) ||
{};
@@ -753,16 +772,20 @@ export function OmoFormFields({
| undefined) || {};
onAgentsChange(importedAgents);
onCategoriesChange(importedCategories);
if (!isSlim && onCategoriesChange) {
onCategoriesChange(importedCategories);
}
onOtherFieldsStrChange(
data.otherFields ? JSON.stringify(data.otherFields, null, 2) : "",
);
setAgentAdvancedDrafts({});
setCategoryAdvancedDrafts({});
setCustomAgents(collectCustomModels(importedAgents, BUILTIN_AGENT_KEYS));
setCustomCategories(
collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS),
);
setCustomAgents(collectCustomModels(importedAgents, builtinAgentKeys));
if (!isSlim) {
setCustomCategories(
collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS),
);
}
setLocalFilePath(data.filePath);
toast.success(
t("omo.importLocalReplaceSuccess", {
@@ -792,7 +815,7 @@ export function OmoFormFields({
) => {
const isAgent = scope === "agent";
const store = isAgent ? agents : categories;
const setter = isAgent ? onAgentsChange : onCategoriesChange;
const setter = isAgent ? onAgentsChange : onCategoriesChange!;
const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts;
const expanded = isAgent ? expandedAgents : expandedCategories;
@@ -866,7 +889,7 @@ export function OmoFormFields({
) => {
const isAgent = scope === "agent";
const store = isAgent ? agents : categories;
const setter = isAgent ? onAgentsChange : onCategoriesChange;
const setter = isAgent ? onAgentsChange : onCategoriesChange!;
const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts;
const expanded = isAgent ? expandedAgents : expandedCategories;
const customs = isAgent ? customAgents : customCategories;
@@ -1153,30 +1176,31 @@ export function OmoFormFields({
),
})}
{renderModelSection({
title: t("omo.categories", { defaultValue: "Categories" }),
isOpen: categoriesOpen,
onToggle: () => setCategoriesOpen(!categoriesOpen),
badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`,
action: renderCustomAddButton(() => addCustomModel("category")),
children: (
<>
{OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)}
{customCategories.length > 0 && (
<>
{renderCustomDivider(
t("omo.customCategories", {
defaultValue: "Custom Categories",
}),
)}
{customCategories.map((c, i) =>
renderCustomModelRow("category", c, i),
)}
</>
)}
</>
),
})}
{!isSlim &&
renderModelSection({
title: t("omo.categories", { defaultValue: "Categories" }),
isOpen: categoriesOpen,
onToggle: () => setCategoriesOpen(!categoriesOpen),
badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`,
action: renderCustomAddButton(() => addCustomModel("category")),
children: (
<>
{OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)}
{customCategories.length > 0 && (
<>
{renderCustomDivider(
t("omo.customCategories", {
defaultValue: "Custom Categories",
}),
)}
{customCategories.map((c, i) =>
renderCustomModelRow("category", c, i),
)}
</>
)}
</>
),
})}
{renderModelSection({
title: t("omo.otherFieldsJson", {
@@ -40,11 +40,18 @@ import {
OMO_BACKGROUND_TASK_PLACEHOLDER,
OMO_BROWSER_AUTOMATION_PLACEHOLDER,
OMO_CLAUDE_CODE_PLACEHOLDER,
OMO_SLIM_DISABLEABLE_AGENTS,
OMO_SLIM_DISABLEABLE_MCPS,
OMO_SLIM_DISABLEABLE_HOOKS,
OMO_SLIM_DEFAULT_SCHEMA_URL,
} from "@/types/omo";
import {
useOmoGlobalConfig,
useSaveOmoGlobalConfig,
useReadOmoLocalFile,
useOmoSlimGlobalConfig,
useSaveOmoSlimGlobalConfig,
useReadOmoSlimLocalFile,
} from "@/lib/query/omo";
interface PresetOption {
@@ -61,6 +68,7 @@ export interface OmoGlobalConfigFieldsRef {
interface OmoGlobalConfigFieldsProps {
onStateChange?: (config: OmoGlobalConfig) => void;
hideSaveButtons?: boolean;
isSlim?: boolean;
}
type OmoAdvancedFieldKey =
@@ -114,6 +122,11 @@ const OMO_ADVANCED_JSON_FIELDS: ReadonlyArray<{
},
];
const OMO_SLIM_ADVANCED_KEYS: ReadonlySet<OmoAdvancedFieldKey> = new Set([
"lspStr",
"experimentalStr",
]);
function TagListEditor({
label,
values,
@@ -310,12 +323,25 @@ function JsonTextareaField({
export const OmoGlobalConfigFields = forwardRef<
OmoGlobalConfigFieldsRef,
OmoGlobalConfigFieldsProps
>(function OmoGlobalConfigFields({ onStateChange, hideSaveButtons }, ref) {
>(function OmoGlobalConfigFields(
{ onStateChange, hideSaveButtons, isSlim = false },
ref,
) {
const { t } = useTranslation();
const { data: config } = useOmoGlobalConfig();
const saveMutation = useSaveOmoGlobalConfig();
const { data: standardConfig } = useOmoGlobalConfig(!isSlim);
const { data: slimConfig } = useOmoSlimGlobalConfig(isSlim);
const config = isSlim ? slimConfig : standardConfig;
const standardSaveMutation = useSaveOmoGlobalConfig();
const slimSaveMutation = useSaveOmoSlimGlobalConfig();
const saveMutation = isSlim ? slimSaveMutation : standardSaveMutation;
const standardReadLocal = useReadOmoLocalFile();
const slimReadLocal = useReadOmoSlimLocalFile();
const [schemaUrl, setSchemaUrl] = useState(OMO_DEFAULT_SCHEMA_URL);
const defaultSchemaUrl = isSlim
? OMO_SLIM_DEFAULT_SCHEMA_URL
: OMO_DEFAULT_SCHEMA_URL;
const [schemaUrl, setSchemaUrl] = useState(defaultSchemaUrl);
const [sisyphusAgentStr, setSisyphusAgentStr] = useState("");
const [disabledAgents, setDisabledAgents] = useState<string[]>([]);
const [disabledMcps, setDisabledMcps] = useState<string[]>([]);
@@ -330,7 +356,7 @@ export const OmoGlobalConfigFields = forwardRef<
const [loaded, setLoaded] = useState(false);
const applyGlobalState = useCallback((global: OmoGlobalConfig) => {
setSchemaUrl(global.schemaUrl || OMO_DEFAULT_SCHEMA_URL);
setSchemaUrl(global.schemaUrl || defaultSchemaUrl);
setSisyphusAgentStr(
global.sisyphusAgent ? JSON.stringify(global.sisyphusAgent, null, 2) : "",
);
@@ -545,7 +571,7 @@ export const OmoGlobalConfigFields = forwardRef<
placeholder: t("omo.disabledAgentsPlaceholder", {
defaultValue: "Disabled Agents",
}),
presets: OMO_DISABLEABLE_AGENTS,
presets: isSlim ? OMO_SLIM_DISABLEABLE_AGENTS : OMO_DISABLEABLE_AGENTS,
},
{
key: "mcps",
@@ -555,7 +581,7 @@ export const OmoGlobalConfigFields = forwardRef<
placeholder: t("omo.disabledMcpsPlaceholder", {
defaultValue: "Disabled MCPs",
}),
presets: OMO_DISABLEABLE_MCPS,
presets: isSlim ? OMO_SLIM_DISABLEABLE_MCPS : OMO_DISABLEABLE_MCPS,
},
{
key: "hooks",
@@ -565,21 +591,25 @@ export const OmoGlobalConfigFields = forwardRef<
placeholder: t("omo.disabledHooksPlaceholder", {
defaultValue: "Disabled Hooks",
}),
presets: OMO_DISABLEABLE_HOOKS,
presets: isSlim ? OMO_SLIM_DISABLEABLE_HOOKS : OMO_DISABLEABLE_HOOKS,
},
{
key: "skills",
label: t("omo.disabledSkills", { defaultValue: "Skills" }),
values: disabledSkills,
onChange: setDisabledSkills,
placeholder: t("omo.disabledSkillsPlaceholder", {
defaultValue: "Disabled Skills",
}),
presets: OMO_DISABLEABLE_SKILLS,
},
] as const;
...(!isSlim
? [
{
key: "skills" as const,
label: t("omo.disabledSkills", { defaultValue: "Skills" }),
values: disabledSkills,
onChange: setDisabledSkills,
placeholder: t("omo.disabledSkillsPlaceholder", {
defaultValue: "Disabled Skills",
}),
presets: OMO_DISABLEABLE_SKILLS,
},
]
: []),
];
const readLocalFile = useReadOmoLocalFile();
const readLocalFile = isSlim ? slimReadLocal : standardReadLocal;
const handleImportGlobalFromLocal = useCallback(async () => {
try {
@@ -668,25 +698,27 @@ export const OmoGlobalConfigFields = forwardRef<
<Input
value={schemaUrl}
onChange={(e) => setSchemaUrl(e.target.value)}
placeholder={OMO_DEFAULT_SCHEMA_URL}
placeholder={defaultSchemaUrl}
className="text-sm h-8"
/>
</div>
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-2">
<Label className="text-sm font-semibold">
{t("omo.sisyphusAgentConfig", {
defaultValue: "Sisyphus Agent",
})}
</Label>
<Textarea
value={sisyphusAgentStr}
onChange={(e) => setSisyphusAgentStr(e.target.value)}
placeholder={OMO_SISYPHUS_AGENT_PLACEHOLDER}
className="font-mono text-sm"
style={{ minHeight: "140px" }}
/>
</div>
{!isSlim && (
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-2">
<Label className="text-sm font-semibold">
{t("omo.sisyphusAgentConfig", {
defaultValue: "Sisyphus Agent",
})}
</Label>
<Textarea
value={sisyphusAgentStr}
onChange={(e) => setSisyphusAgentStr(e.target.value)}
placeholder={OMO_SISYPHUS_AGENT_PLACEHOLDER}
className="font-mono text-sm"
style={{ minHeight: "140px" }}
/>
</div>
)}
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-3">
<div className="flex items-center gap-2">
@@ -715,7 +747,9 @@ export const OmoGlobalConfigFields = forwardRef<
<Label className="text-sm font-semibold">
{t("omo.advanced", { defaultValue: "Advanced Settings" })}
</Label>
{OMO_ADVANCED_JSON_FIELDS.map((field) => (
{OMO_ADVANCED_JSON_FIELDS.filter(
(field) => !isSlim || OMO_SLIM_ADVANCED_KEYS.has(field.key),
).map((field) => (
<JsonTextareaField
key={field.key}
label={t(field.labelKey, { defaultValue: field.defaultLabel })}
+46 -21
View File
@@ -78,7 +78,7 @@ import {
useOmoDraftState,
useOpenclawFormState,
} from "./hooks";
import { useOmoGlobalConfig } from "@/lib/query/omo";
import { useOmoGlobalConfig, useOmoSlimGlobalConfig } from "@/lib/query/omo";
import {
CLAUDE_DEFAULT_CONFIG,
CODEX_DEFAULT_CONFIG,
@@ -184,7 +184,15 @@ export function ProviderForm({
initialCategory: initialData?.category,
});
const isOmoCategory = appId === "opencode" && category === "omo";
const { data: queriedOmoGlobalConfig } = useOmoGlobalConfig(isOmoCategory);
const isOmoSlimCategory = appId === "opencode" && category === "omo-slim";
const isAnyOmoCategory = isOmoCategory || isOmoSlimCategory;
const { data: queriedStandardOmoGlobalConfig } =
useOmoGlobalConfig(isOmoCategory);
const { data: queriedSlimOmoGlobalConfig } =
useOmoSlimGlobalConfig(isOmoSlimCategory);
const queriedOmoGlobalConfig = isOmoSlimCategory
? queriedSlimOmoGlobalConfig
: queriedStandardOmoGlobalConfig;
useEffect(() => {
setSelectedPresetId(initialData ? null : "custom");
@@ -495,7 +503,7 @@ export function ProviderForm({
omoModelVariantsMap,
omoPresetMetaMap,
existingOpencodeKeys,
} = useOmoModelSource({ isOmoCategory, providerId });
} = useOmoModelSource({ isOmoCategory: isAnyOmoCategory, providerId });
const opencodeForm = useOpencodeFormState({
initialData,
@@ -506,7 +514,8 @@ export function ProviderForm({
});
const initialOmoSettings =
appId === "opencode" && initialData?.category === "omo"
appId === "opencode" &&
(initialData?.category === "omo" || initialData?.category === "omo-slim")
? (initialData.settingsConfig as Record<string, unknown> | undefined)
: undefined;
@@ -677,13 +686,19 @@ export function ProviderForm({
} catch (err) {
settingsConfig = values.settingsConfig.trim();
}
} else if (appId === "opencode" && category === "omo") {
} else if (
appId === "opencode" &&
(category === "omo" || category === "omo-slim")
) {
const omoConfig: Record<string, unknown> = {};
omoConfig.useCommonConfig = omoDraft.useOmoCommonConfig;
if (Object.keys(omoDraft.omoAgents).length > 0) {
omoConfig.agents = omoDraft.omoAgents;
}
if (Object.keys(omoDraft.omoCategories).length > 0) {
if (
category === "omo" &&
Object.keys(omoDraft.omoCategories).length > 0
) {
omoConfig.categories = omoDraft.omoCategories;
}
if (omoDraft.omoOtherFieldsStr.trim()) {
@@ -1334,19 +1349,25 @@ export function ProviderForm({
/>
)}
{appId === "opencode" && category === "omo" && (
<OmoFormFields
modelOptions={omoModelOptions}
modelVariantsMap={omoModelVariantsMap}
presetMetaMap={omoPresetMetaMap}
agents={omoDraft.omoAgents}
onAgentsChange={omoDraft.setOmoAgents}
categories={omoDraft.omoCategories}
onCategoriesChange={omoDraft.setOmoCategories}
otherFieldsStr={omoDraft.omoOtherFieldsStr}
onOtherFieldsStrChange={omoDraft.setOmoOtherFieldsStr}
/>
)}
{appId === "opencode" &&
(category === "omo" || category === "omo-slim") && (
<OmoFormFields
modelOptions={omoModelOptions}
modelVariantsMap={omoModelVariantsMap}
presetMetaMap={omoPresetMetaMap}
agents={omoDraft.omoAgents}
onAgentsChange={omoDraft.setOmoAgents}
categories={
category === "omo" ? omoDraft.omoCategories : undefined
}
onCategoriesChange={
category === "omo" ? omoDraft.setOmoCategories : undefined
}
otherFieldsStr={omoDraft.omoOtherFieldsStr}
onOtherFieldsStrChange={omoDraft.setOmoOtherFieldsStr}
isSlim={category === "omo-slim"}
/>
)}
{/* OpenClaw 专属字段 */}
{appId === "openclaw" && (
@@ -1408,7 +1429,8 @@ export function ProviderForm({
/>
{settingsConfigErrorField}
</>
) : appId === "opencode" && category === "omo" ? (
) : appId === "opencode" &&
(category === "omo" || category === "omo-slim") ? (
<OmoCommonConfigEditor
previewValue={omoDraft.mergedOmoJsonPreview}
useCommonConfig={omoDraft.useOmoCommonConfig}
@@ -1421,8 +1443,11 @@ export function ProviderForm({
onGlobalConfigStateChange={omoDraft.setOmoGlobalState}
globalConfigRef={omoDraft.omoGlobalConfigRef}
fieldsKey={omoDraft.omoFieldsKey}
isSlim={category === "omo-slim"}
/>
) : appId === "opencode" && category !== "omo" ? (
) : appId === "opencode" &&
category !== "omo" &&
category !== "omo-slim" ? (
<>
<div className="space-y-2">
<Label htmlFor="settingsConfig">{t("provider.configJson")}</Label>
@@ -2,7 +2,11 @@ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import type { OmoGlobalConfig } from "@/types/omo";
import { mergeOmoConfigPreview } from "@/types/omo";
import {
mergeOmoConfigPreview,
mergeOmoSlimConfigPreview,
buildOmoSlimProfilePreview,
} from "@/types/omo";
import { type OmoGlobalConfigFieldsRef } from "../OmoGlobalConfigFields";
import * as configApi from "@/lib/api/config";
import {
@@ -54,6 +58,8 @@ export function useOmoDraftState({
category,
}: UseOmoDraftStateParams): OmoDraftState {
const { t } = useTranslation();
const isSlim = category === "omo-slim";
const commonConfigKey = isSlim ? "omo_slim" : "omo";
const [omoAgents, setOmoAgents] = useState<
Record<string, Record<string, unknown>>
@@ -93,6 +99,14 @@ export function useOmoDraftState({
const mergedOmoJsonPreview = useMemo(() => {
if (useOmoCommonConfig) {
if (isSlim) {
const merged = mergeOmoSlimConfigPreview(
effectiveOmoGlobalConfig,
omoAgents,
omoOtherFieldsStr,
);
return JSON.stringify(merged, null, 2);
}
const merged = mergeOmoConfigPreview(
effectiveOmoGlobalConfig,
omoAgents,
@@ -101,6 +115,13 @@ export function useOmoDraftState({
);
return JSON.stringify(merged, null, 2);
} else {
if (isSlim) {
return JSON.stringify(
buildOmoSlimProfilePreview(omoAgents, omoOtherFieldsStr),
null,
2,
);
}
return JSON.stringify(
buildOmoProfilePreview(omoAgents, omoCategories, omoOtherFieldsStr),
null,
@@ -113,16 +134,22 @@ export function useOmoDraftState({
omoAgents,
omoCategories,
omoOtherFieldsStr,
isSlim,
]);
// Auto-detect whether common config has content for new OMO profiles
// Auto-detect whether common config has content for new OMO/OMO Slim profiles
useEffect(() => {
if (appId !== "opencode" || category !== "omo" || isEditMode) return;
if (
appId !== "opencode" ||
(category !== "omo" && category !== "omo-slim") ||
isEditMode
)
return;
let active = true;
(async () => {
let next = false;
try {
const raw = await configApi.getCommonConfigSnippet("omo");
const raw = await configApi.getCommonConfigSnippet(commonConfigKey);
if (raw) {
const parsed = JSON.parse(raw) as Record<string, unknown>;
next = Object.keys(parsed).some(
@@ -135,14 +162,17 @@ export function useOmoDraftState({
return () => {
active = false;
};
}, [appId, category, isEditMode]);
}, [appId, category, isEditMode, commonConfigKey]);
const handleOmoGlobalConfigSave = useCallback(async () => {
if (!omoGlobalConfigRef.current) return;
setIsOmoSaving(true);
try {
const config = omoGlobalConfigRef.current.buildCurrentConfigStrict();
await configApi.setCommonConfigSnippet("omo", JSON.stringify(config));
await configApi.setCommonConfigSnippet(
commonConfigKey,
JSON.stringify(config),
);
setIsOmoConfigModalOpen(false);
toast.success(
t("omo.globalConfigSaved", { defaultValue: "Global config saved" }),
@@ -152,7 +182,7 @@ export function useOmoDraftState({
} finally {
setIsOmoSaving(false);
}
}, [t]);
}, [t, commonConfigKey]);
const handleOmoEditClick = useCallback(() => {
setOmoFieldsKey((k) => k + 1);
@@ -133,7 +133,7 @@ export function useOmoModelSource({
const parseFailedProviders: string[] = [];
for (const [providerKey, provider] of Object.entries(allProviders)) {
if (provider.category === "omo") {
if (provider.category === "omo" || provider.category === "omo-slim") {
continue;
}
if (liveSet && !liveSet.has(providerKey)) {
+13
View File
@@ -1129,4 +1129,17 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
iconColor: "#8B5CF6",
isCustomTemplate: true,
},
{
name: "Oh My OpenCode Slim",
websiteUrl: "https://github.com/alvinunreal/oh-my-opencode-slim",
settingsConfig: {
npm: "",
options: {},
models: {},
},
category: "omo-slim" as ProviderCategory,
icon: "opencode",
iconColor: "#6366F1",
isCustomTemplate: true,
},
];
+16
View File
@@ -1858,6 +1858,22 @@
"unspecifiedLow": "Uncategorized low-effort task category for tasks that don't fit other categories with small workload. Defaults to Claude Sonnet 4.5.",
"unspecifiedHigh": "Uncategorized high-effort task category for tasks that don't fit other categories with large workload. Defaults to Claude Opus 4.6 max variant.",
"writing": "Writing category for documentation, prose and technical writing. Defaults to Gemini 3 Flash fast generation model."
},
"slimAgentDesc": {
"orchestrator": "Orchestrator",
"oracle": "Oracle",
"librarian": "Librarian",
"explorer": "Explorer",
"designer": "Designer",
"fixer": "Fixer"
},
"slimAgentTooltip": {
"orchestrator": "Writes executable code, orchestrates multi-agent workflow, summons experts",
"oracle": "Root cause analysis, architecture review, debugging guidance (read-only)",
"librarian": "Documentation lookup, GitHub code search (read-only)",
"explorer": "Regex search, AST pattern matching, file discovery (read-only)",
"designer": "Modern responsive design, CSS/Tailwind expertise",
"fixer": "Code implementation, refactoring, testing, verification"
}
},
"openclawConfig": {
+16
View File
@@ -1839,6 +1839,22 @@
"unspecifiedLow": "未分類の低作業量タスクカテゴリ。他のカテゴリに該当せず作業量が小さいタスクに適用。デフォルトで Claude Sonnet 4.5 を使用。",
"unspecifiedHigh": "未分類の高作業量タスクカテゴリ。他のカテゴリに該当せず作業量が大きいタスクに適用。デフォルトで Claude Opus 4.6 の max バリアントを使用。",
"writing": "ライティングカテゴリ。ドキュメント、散文、技術文書に特化。デフォルトで Gemini 3 Flash 高速生成モデルを使用。"
},
"slimAgentDesc": {
"orchestrator": "オーケストレーター",
"oracle": "オラクル",
"librarian": "ライブラリアン",
"explorer": "エクスプローラー",
"designer": "デザイナー",
"fixer": "フィクサー"
},
"slimAgentTooltip": {
"orchestrator": "実行コードの作成、マルチエージェントワークフローの調整、エキスパートの召喚",
"oracle": "根本原因分析、アーキテクチャレビュー、デバッグガイダンス(読み取り専用)",
"librarian": "ドキュメント検索、GitHubコード検索(読み取り専用)",
"explorer": "正規表現検索、ASTパターンマッチング、ファイル検出(読み取り専用)",
"designer": "モダンなレスポンシブデザイン、CSS/Tailwindの専門知識",
"fixer": "コード実装、リファクタリング、テスト、検証"
}
},
"openclawConfig": {
+16
View File
@@ -1858,6 +1858,22 @@
"unspecifiedLow": "未归类低工作量任务类别,适用于不适合其他类别且工作量较小的任务,默认使用 Claude Sonnet 4.5。",
"unspecifiedHigh": "未归类高工作量任务类别,适用于不适合其他类别且工作量较大的任务,默认使用 Claude Opus 4.6 的最大变体。",
"writing": "写作类别,专注于文档、散文和技术写作,默认使用 Gemini 3 Flash 快速生成模型。"
},
"slimAgentDesc": {
"orchestrator": "编排者",
"oracle": "神谕者",
"librarian": "图书管理员",
"explorer": "探索者",
"designer": "设计师",
"fixer": "修复者"
},
"slimAgentTooltip": {
"orchestrator": "编写执行代码,编排多代理工作流,召唤专家",
"oracle": "根本原因分析、架构审查、调试指导(只读)",
"librarian": "文档查询、GitHub 代码搜索(只读)",
"explorer": "正则搜索、AST 模式匹配、文件发现(只读)",
"designer": "现代响应式设计、CSS/Tailwind 精通",
"fixer": "代码实现、重构、测试、验证"
}
},
"openclawConfig": {
+1 -1
View File
@@ -1,7 +1,7 @@
// 配置相关 API
import { invoke } from "@tauri-apps/api/core";
export type AppType = "claude" | "codex" | "gemini" | "omo";
export type AppType = "claude" | "codex" | "gemini" | "omo" | "omo_slim";
/**
* 获取 Claude 通用配置片段(已废弃,使用 getCommonConfigSnippet
+10
View File
@@ -8,3 +8,13 @@ export const omoApi = {
getOmoProviderCount: (): Promise<number> => invoke("get_omo_provider_count"),
disableCurrentOmo: (): Promise<void> => invoke("disable_current_omo"),
};
export const omoSlimApi = {
readLocalFile: (): Promise<OmoLocalFileData> =>
invoke("read_omo_slim_local_file"),
getCurrentProviderId: (): Promise<string> =>
invoke("get_current_omo_slim_provider_id"),
getProviderCount: (): Promise<number> =>
invoke("get_omo_slim_provider_count"),
disableCurrent: (): Promise<void> => invoke("disable_current_omo_slim"),
};
+100 -1
View File
@@ -1,5 +1,5 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { omoApi } from "@/lib/api/omo";
import { omoApi, omoSlimApi } from "@/lib/api/omo";
import * as configApi from "@/lib/api/config";
import type { OmoGlobalConfig } from "@/types/omo";
@@ -93,3 +93,102 @@ export function useDisableCurrentOmo() {
onSuccess: () => invalidateOmoQueries(queryClient),
});
}
// ============================================================================
// OMO Slim query hooks
// ============================================================================
export const omoSlimKeys = {
all: ["omo-slim"] as const,
globalConfig: () => [...omoSlimKeys.all, "global-config"] as const,
currentProviderId: () => [...omoSlimKeys.all, "current-provider-id"] as const,
providerCount: () => [...omoSlimKeys.all, "provider-count"] as const,
};
function invalidateOmoSlimQueries(
queryClient: ReturnType<typeof useQueryClient>,
) {
queryClient.invalidateQueries({ queryKey: omoSlimKeys.globalConfig() });
queryClient.invalidateQueries({ queryKey: ["providers"] });
queryClient.invalidateQueries({
queryKey: omoSlimKeys.currentProviderId(),
});
queryClient.invalidateQueries({ queryKey: omoSlimKeys.providerCount() });
}
export function useOmoSlimGlobalConfig(enabled = true) {
return useQuery({
queryKey: omoSlimKeys.globalConfig(),
enabled,
queryFn: async (): Promise<OmoGlobalConfig> => {
const raw = await configApi.getCommonConfigSnippet("omo_slim");
if (!raw) {
return {
id: "global",
disabledAgents: [],
disabledMcps: [],
disabledHooks: [],
disabledSkills: [],
updatedAt: new Date().toISOString(),
};
}
try {
return JSON.parse(raw) as OmoGlobalConfig;
} catch (error) {
console.warn(
"[omo-slim] invalid global config json, fallback to defaults",
error,
);
return {
id: "global",
disabledAgents: [],
disabledMcps: [],
disabledHooks: [],
disabledSkills: [],
updatedAt: new Date().toISOString(),
};
}
},
});
}
export function useCurrentOmoSlimProviderId(enabled = true) {
return useQuery({
queryKey: omoSlimKeys.currentProviderId(),
queryFn: omoSlimApi.getCurrentProviderId,
enabled,
});
}
export function useOmoSlimProviderCount(enabled = true) {
return useQuery({
queryKey: omoSlimKeys.providerCount(),
queryFn: omoSlimApi.getProviderCount,
enabled,
});
}
export function useSaveOmoSlimGlobalConfig() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: OmoGlobalConfig) => {
const jsonStr = JSON.stringify(input);
await configApi.setCommonConfigSnippet("omo_slim", jsonStr);
},
onSuccess: () => invalidateOmoSlimQueries(queryClient),
});
}
export function useReadOmoSlimLocalFile() {
return useMutation({
mutationFn: () => omoSlimApi.readLocalFile(),
});
}
export function useDisableCurrentOmoSlim() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => omoSlimApi.disableCurrent(),
onSuccess: () => invalidateOmoSlimQueries(queryClient),
});
}
+2 -1
View File
@@ -4,7 +4,8 @@ export type ProviderCategory =
| "aggregator" // 聚合网站
| "third_party" // 第三方供应商
| "custom" // 自定义
| "omo"; // Oh My OpenCode
| "omo" // Oh My OpenCode
| "omo-slim"; // Oh My OpenCode Slim
export interface Provider {
id: string;
+136
View File
@@ -327,6 +327,142 @@ export function parseOmoOtherFieldsObject(
return parsed as Record<string, unknown>;
}
// ============================================================================
// OMO Slim (oh-my-opencode-slim) definitions
// ============================================================================
export const OMO_SLIM_BUILTIN_AGENTS: OmoAgentDef[] = [
{
key: "orchestrator",
display: "Orchestrator",
descKey: "omo.slimAgentDesc.orchestrator",
tooltipKey: "omo.slimAgentTooltip.orchestrator",
recommended: "kimi-for-coding/k2p5",
group: "main",
},
{
key: "oracle",
display: "Oracle",
descKey: "omo.slimAgentDesc.oracle",
tooltipKey: "omo.slimAgentTooltip.oracle",
recommended: "openai/gpt-5.2-codex",
group: "sub",
},
{
key: "librarian",
display: "Librarian",
descKey: "omo.slimAgentDesc.librarian",
tooltipKey: "omo.slimAgentTooltip.librarian",
recommended: "openai/gpt-5.1-codex-mini",
group: "sub",
},
{
key: "explorer",
display: "Explorer",
descKey: "omo.slimAgentDesc.explorer",
tooltipKey: "omo.slimAgentTooltip.explorer",
recommended: "openai/gpt-5.1-codex-mini",
group: "sub",
},
{
key: "designer",
display: "Designer",
descKey: "omo.slimAgentDesc.designer",
tooltipKey: "omo.slimAgentTooltip.designer",
recommended: "kimi-for-coding/k2p5",
group: "sub",
},
{
key: "fixer",
display: "Fixer",
descKey: "omo.slimAgentDesc.fixer",
tooltipKey: "omo.slimAgentTooltip.fixer",
recommended: "openai/gpt-5.1-codex-mini",
group: "sub",
},
];
export const OMO_SLIM_DISABLEABLE_AGENTS = [
{ value: "orchestrator", label: "Orchestrator" },
{ value: "oracle", label: "Oracle" },
{ value: "librarian", label: "Librarian" },
{ value: "explorer", label: "Explorer" },
{ value: "designer", label: "Designer" },
{ value: "fixer", label: "Fixer" },
] as const;
export const OMO_SLIM_DISABLEABLE_MCPS = [
{ value: "context7", label: "context7" },
{ value: "grep_app", label: "grep_app" },
{ value: "websearch", label: "websearch" },
] as const;
export const OMO_SLIM_DISABLEABLE_HOOKS = [
{ value: "auto-update-checker", label: "auto-update-checker" },
{ value: "phase-reminder", label: "phase-reminder" },
{ value: "post-read-nudge", label: "post-read-nudge" },
] as const;
export const OMO_SLIM_DEFAULT_SCHEMA_URL =
"https://raw.githubusercontent.com/alvinunreal/oh-my-opencode-slim/master/assets/oh-my-opencode-slim.schema.json";
export function mergeOmoSlimConfigPreview(
global: OmoGlobalConfig | undefined,
agents: Record<string, Record<string, unknown>>,
otherFieldsStr: string,
): Record<string, unknown> {
const result: Record<string, unknown> = {};
if (global) {
if (global.schemaUrl) result["$schema"] = global.schemaUrl;
if (global.disabledAgents?.length)
result["disabled_agents"] = global.disabledAgents;
if (global.disabledMcps?.length)
result["disabled_mcps"] = global.disabledMcps;
if (global.disabledHooks?.length)
result["disabled_hooks"] = global.disabledHooks;
if (global.otherFields) {
for (const [k, v] of Object.entries(global.otherFields)) {
result[k] = v;
}
}
}
if (Object.keys(agents).length > 0) result["agents"] = agents;
try {
const other = parseOmoOtherFieldsObject(otherFieldsStr);
if (other) {
for (const [k, v] of Object.entries(other)) {
result[k] = v;
}
}
} catch {}
return result;
}
export function buildOmoSlimProfilePreview(
agents: Record<string, Record<string, unknown>>,
otherFieldsStr: string,
): Record<string, unknown> {
const result: Record<string, unknown> = {};
if (Object.keys(agents).length > 0) result["agents"] = agents;
try {
const other = parseOmoOtherFieldsObject(otherFieldsStr);
if (other) {
for (const [k, v] of Object.entries(other)) {
result[k] = v;
}
}
} catch {}
return result;
}
export function mergeOmoConfigPreview(
global: OmoGlobalConfig,
agents: Record<string, Record<string, unknown>>,