feat: add managed system prompt optimization
Add an opt-in OpenCode plugin that replaces the built-in provider behavioral prompt with a minimal identity while preserving environment, project, MCP, skill, history, and tool context. Track the active agent per session and apply the transform only to build and plan. Keep plan/build mode reminders and permission enforcement owned by OpenCode, leave all other agents untouched, and fail safely when the expected prompt boundary is absent. Expose the feature in Behavior settings with localized guidance, explicit Save + Reload application, settings search integration, persisted boolean validation, and managed-runtime lifecycle composition that does not load the plugin while disabled or on external OpenCode servers. Document the runtime contract and cover plugin materialization, config preservation, build/plan selection, agent switching, unknown prompt formats, and settings sanitization.
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
} from '@/lib/responseStyle';
|
||||
import type { DesktopSettings } from '@/lib/desktop';
|
||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
|
||||
import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLayout';
|
||||
import {
|
||||
SettingsSection,
|
||||
@@ -43,6 +44,7 @@ type ResponseStyleValue = ResponseStylePreset | 'custom';
|
||||
|
||||
type BehaviorSettingsState = {
|
||||
prompt: string;
|
||||
optimizeSystemPrompt: boolean;
|
||||
responseStyleEnabled: boolean;
|
||||
responseStylePreset: ResponseStyleValue;
|
||||
responseStyleCustomInstructions: string;
|
||||
@@ -50,6 +52,7 @@ type BehaviorSettingsState = {
|
||||
|
||||
const DEFAULT_BEHAVIOR_SETTINGS: BehaviorSettingsState = {
|
||||
prompt: '',
|
||||
optimizeSystemPrompt: false,
|
||||
responseStyleEnabled: false,
|
||||
responseStylePreset: 'concise',
|
||||
responseStyleCustomInstructions: '',
|
||||
@@ -99,12 +102,15 @@ const saveBehaviorSetting = async (settings: Partial<DesktopSettings>, fallbackE
|
||||
export const BehaviorPage: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const [prompt, setPrompt] = React.useState('');
|
||||
const [optimizeSystemPrompt, setOptimizeSystemPrompt] = React.useState(false);
|
||||
const [responseStyleEnabled, setResponseStyleEnabled] = React.useState(DEFAULT_BEHAVIOR_SETTINGS.responseStyleEnabled);
|
||||
const [responseStylePreset, setResponseStylePreset] = React.useState<ResponseStyleValue>(DEFAULT_BEHAVIOR_SETTINGS.responseStylePreset);
|
||||
const [responseStyleCustomInstructions, setResponseStyleCustomInstructions] = React.useState(DEFAULT_BEHAVIOR_SETTINGS.responseStyleCustomInstructions);
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const [isApplyingPromptOptimization, setIsApplyingPromptOptimization] = React.useState(false);
|
||||
const [initialPrompt, setInitialPrompt] = React.useState('');
|
||||
const [initialOptimizeSystemPrompt, setInitialOptimizeSystemPrompt] = React.useState(false);
|
||||
const lastSavedResponseStyleRef = React.useRef<{
|
||||
enabled: boolean;
|
||||
preset: ResponseStyleValue;
|
||||
@@ -134,6 +140,7 @@ export const BehaviorPage: React.FC = () => {
|
||||
const data = await settingsRes.json();
|
||||
nextSettings = {
|
||||
...nextSettings,
|
||||
optimizeSystemPrompt: data.optimizeSystemPrompt === true,
|
||||
responseStyleEnabled: data.responseStyleEnabled === true,
|
||||
responseStylePreset: sanitizeResponseStylePreset(data.responseStylePreset),
|
||||
responseStyleCustomInstructions: typeof data.responseStyleCustomInstructions === 'string'
|
||||
@@ -153,6 +160,8 @@ export const BehaviorPage: React.FC = () => {
|
||||
}
|
||||
|
||||
setPrompt(nextSettings.prompt);
|
||||
setOptimizeSystemPrompt(nextSettings.optimizeSystemPrompt);
|
||||
setInitialOptimizeSystemPrompt(nextSettings.optimizeSystemPrompt);
|
||||
setResponseStyleEnabled(nextSettings.responseStyleEnabled);
|
||||
setResponseStylePreset(nextSettings.responseStylePreset);
|
||||
setResponseStyleCustomInstructions(nextSettings.responseStyleCustomInstructions);
|
||||
@@ -212,6 +221,7 @@ export const BehaviorPage: React.FC = () => {
|
||||
|
||||
const responseStylePreview = getResponseStylePreview(responseStylePreset, responseStyleCustomInstructions);
|
||||
const isPromptDirty = prompt !== initialPrompt;
|
||||
const isPromptOptimizationDirty = optimizeSystemPrompt !== initialOptimizeSystemPrompt;
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsSaving(true);
|
||||
@@ -246,12 +256,68 @@ export const BehaviorPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSavePromptOptimization = async () => {
|
||||
setIsApplyingPromptOptimization(true);
|
||||
try {
|
||||
await saveBehaviorSetting(
|
||||
{ optimizeSystemPrompt },
|
||||
t('settings.behavior.page.toast.saveFailed'),
|
||||
);
|
||||
setInitialOptimizeSystemPrompt(optimizeSystemPrompt);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('settings.behavior.page.toast.saveFailed');
|
||||
toast.error(message);
|
||||
setIsApplyingPromptOptimization(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await reloadOpenCodeConfiguration({
|
||||
message: t('settings.behavior.page.systemPromptOptimization.restarting'),
|
||||
mode: 'projects',
|
||||
scopes: ['all'],
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('settings.behavior.page.toast.saveFailed');
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setIsApplyingPromptOptimization(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={t('settings.behavior.page.title')}
|
||||
description={t('settings.page.behavior.description')}
|
||||
showSaveStatus
|
||||
>
|
||||
<SettingsSection
|
||||
title={t('settings.behavior.page.section.systemPromptOptimization')}
|
||||
divider={false}
|
||||
settingsItem="behavior.system-prompt-optimization"
|
||||
contentClassName="space-y-3"
|
||||
>
|
||||
<SettingsCheckboxRow
|
||||
checked={optimizeSystemPrompt}
|
||||
onChange={setOptimizeSystemPrompt}
|
||||
disabled={isLoading || isApplyingPromptOptimization}
|
||||
label={t('settings.behavior.page.systemPromptOptimization.enable')}
|
||||
ariaLabel={t('settings.behavior.page.systemPromptOptimization.enableAria')}
|
||||
info={t('settings.behavior.page.systemPromptOptimization.info')}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
size="xs"
|
||||
onClick={() => void handleSavePromptOptimization()}
|
||||
disabled={isLoading || isApplyingPromptOptimization || !isPromptOptimizationDirty}
|
||||
className="!font-normal"
|
||||
>
|
||||
{isApplyingPromptOptimization
|
||||
? t('settings.common.actions.saving')
|
||||
: t('settings.openchamber.opencodeCli.actions.saveAndReload')}
|
||||
</Button>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
title={t('settings.behavior.page.section.systemPrompt')}
|
||||
info={(
|
||||
@@ -264,7 +330,6 @@ export const BehaviorPage: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
divider={false}
|
||||
settingsItem="behavior.system-prompt"
|
||||
contentClassName="space-y-3"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user