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"
|
||||
>
|
||||
|
||||
@@ -145,6 +145,7 @@ export type DesktopSettings = {
|
||||
inputSpellcheckEnabled?: boolean;
|
||||
showOpenCodeUpdateNotifications?: boolean;
|
||||
agentControlToolEnabled?: boolean;
|
||||
optimizeSystemPrompt?: boolean;
|
||||
openCodeUpdateToastDismissedVersion?: string;
|
||||
showToolFileIcons?: boolean;
|
||||
codeBlockLineWrap?: boolean;
|
||||
|
||||
@@ -579,6 +579,11 @@ export const settingsDict = {
|
||||
'settings.behavior.page.warning.title': 'Global rules are combined with project rules',
|
||||
'settings.behavior.page.warning.description': 'Changes made here update {path}. OpenCode also includes any project-level AGENTS.md rules when they exist.',
|
||||
'settings.behavior.page.section.systemPrompt': 'Global AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': 'System prompt optimization',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': 'Optimize system prompt size',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': 'Optimize OpenCode system prompt size',
|
||||
'settings.behavior.page.systemPromptOptimization.info': 'Reduces the system prompt by an estimated 40% for the build and plan agents. Other agents are not changed. This can remove custom definitions that override build or plan, so do not enable it for workflows that customize those agents. Restarting OpenCode applies the change.',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': 'Restarting OpenCode to apply system prompt optimization…',
|
||||
'settings.behavior.page.field.systemPromptPlaceholder': 'You are a helpful AI assistant...\n\nUse this space to define absolute rules for how the AI should behave across all sessions and providers.',
|
||||
'settings.behavior.page.section.responseStyle': 'Response style',
|
||||
'settings.behavior.page.responseStyle.tooltip': 'When enabled, these instructions guide how the assistant responds in each new conversation. They are sent with your first message and do not change your global AGENTS.md rules.',
|
||||
|
||||
@@ -546,6 +546,11 @@ export const settingsDict = {
|
||||
"settings.behavior.page.warning.title": "Las reglas globales se combinan con las reglas del proyecto",
|
||||
"settings.behavior.page.warning.description": "Los cambios realizados aquí actualizan {path}. OpenCode también incluye cualquier AGENTS.md a nivel de proyecto cuando existe.",
|
||||
"settings.behavior.page.section.systemPrompt": "AGENTS.md global",
|
||||
"settings.behavior.page.section.systemPromptOptimization": "Optimización del prompt del sistema",
|
||||
"settings.behavior.page.systemPromptOptimization.enable": "Optimizar el tamaño del prompt del sistema",
|
||||
"settings.behavior.page.systemPromptOptimization.enableAria": "Optimizar el tamaño del prompt del sistema de OpenCode",
|
||||
"settings.behavior.page.systemPromptOptimization.info": "Reduce el prompt del sistema aproximadamente un 40 % para los agentes build y plan. Los demás agentes no cambian. Puede eliminar definiciones personalizadas que sobrescriban build o plan, así que no lo actives en esos flujos de trabajo. El cambio se aplica al reiniciar OpenCode.",
|
||||
"settings.behavior.page.systemPromptOptimization.restarting": "Reiniciando OpenCode para aplicar la optimización del prompt del sistema…",
|
||||
"settings.behavior.page.field.systemPromptPlaceholder": "Eres un asistente de IA útil...\n\nUsa este espacio para definir reglas absolutas sobre cómo la IA debe comportarse en todas las sesiones y proveedores.",
|
||||
"settings.behavior.page.section.responseStyle": "Estilo de respuesta",
|
||||
"settings.behavior.page.responseStyle.tooltip": "Cuando está habilitado, estas instrucciones guían cómo responde el asistente en cada conversación nueva. Se envían con tu primer mensaje y no cambian tus reglas globales de AGENTS.md.",
|
||||
|
||||
@@ -467,6 +467,11 @@ export const settingsDict = {
|
||||
'settings.behavior.page.warning.title': 'Les règles globales sont combinées avec les règles du projet',
|
||||
'settings.behavior.page.warning.description': 'Les modifications apportées ici mettent à jour {path}. OpenCode inclut également toutes les règles AGENTS.md au niveau du projet lorsqu\'elles existent.',
|
||||
'settings.behavior.page.section.systemPrompt': 'Mondial AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': 'Optimisation du prompt système',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': 'Optimiser la taille du prompt système',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': 'Optimiser la taille du prompt système d’OpenCode',
|
||||
'settings.behavior.page.systemPromptOptimization.info': 'Réduit le prompt système d’environ 40 % pour les agents build et plan. Les autres agents ne sont pas modifiés. Cela peut supprimer les définitions personnalisées qui remplacent build ou plan ; ne l’activez donc pas pour ces workflows. Le redémarrage d’OpenCode applique la modification.',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': 'Redémarrage d’OpenCode pour appliquer l’optimisation du prompt système…',
|
||||
'settings.behavior.page.field.systemPromptPlaceholder': 'Vous êtes un assistant IA utile...\n\nUtilisez cet espace pour définir des règles absolues sur la façon dont l\'IA doit se comporter dans toutes les sessions et tous les fournisseurs.',
|
||||
'settings.behavior.page.section.responseStyle': 'Style de réponse',
|
||||
'settings.behavior.page.responseStyle.tooltip': 'Lorsqu\'elles sont activées, ces instructions guident la manière dont l\'assistant répond à chaque nouvelle conversation. Ils sont envoyés avec votre premier message et ne modifient pas vos règles globales AGENTS.md.',
|
||||
|
||||
@@ -579,6 +579,11 @@ export const settingsDict = {
|
||||
'settings.behavior.page.warning.title': 'グローバルルールはプロジェクトルールと組み合わされます',
|
||||
'settings.behavior.page.warning.description': 'ここでの変更は {path} を更新します。OpenCode はプロジェクトレベルの AGENTS.md ルールも存在する場合は含めます。',
|
||||
'settings.behavior.page.section.systemPrompt': 'グローバル AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': 'システムプロンプトの最適化',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': 'システムプロンプトのサイズを最適化',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': 'OpenCode のシステムプロンプトサイズを最適化',
|
||||
'settings.behavior.page.systemPromptOptimization.info': 'build と plan エージェントのシステムプロンプトを約 40% 削減します。他のエージェントは変更されません。build または plan を上書きするカスタム定義が削除される可能性があるため、そのようなワークフローでは有効にしないでください。OpenCode の再起動後に変更が適用されます。',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': 'システムプロンプトの最適化を適用するため OpenCode を再起動しています…',
|
||||
'settings.behavior.page.field.systemPromptPlaceholder': 'あなたは役立つ AI アシスタントです...\n\nこのスペースを使用して、すべての Session と Provider にわたって AI の動作方法に関する絶対的なルールを定義してください。',
|
||||
'settings.behavior.page.section.responseStyle': '応答スタイル',
|
||||
'settings.behavior.page.responseStyle.tooltip': '有効にすると、これらの指示は新しい会話ごとにアシスタントの応答方法をガイドします。最初のメッセージとともに送信され、グローバル AGENTS.md ルールは変更しません。',
|
||||
|
||||
@@ -546,6 +546,11 @@ export const settingsDict = {
|
||||
'settings.behavior.page.warning.title': '전역 규칙은 프로젝트 규칙과 함께 사용됩니다',
|
||||
'settings.behavior.page.warning.description': '여기에서 변경한 내용은 {path}를 업데이트합니다. 프로젝트 수준의 AGENTS.md가 있으면 OpenCode도 함께 포함합니다.',
|
||||
'settings.behavior.page.section.systemPrompt': '전역 AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': '시스템 프롬프트 최적화',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': '시스템 프롬프트 크기 최적화',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': 'OpenCode 시스템 프롬프트 크기 최적화',
|
||||
'settings.behavior.page.systemPromptOptimization.info': 'build 및 plan 에이전트의 시스템 프롬프트를 약 40% 줄입니다. 다른 에이전트는 변경되지 않습니다. build 또는 plan을 재정의하는 사용자 지정 정의가 제거될 수 있으므로 이러한 워크플로에서는 활성화하지 마세요. OpenCode를 다시 시작하면 변경 사항이 적용됩니다.',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': '시스템 프롬프트 최적화를 적용하기 위해 OpenCode를 다시 시작하는 중…',
|
||||
'settings.behavior.page.field.systemPromptPlaceholder': '당신은 유용한 AI 어시스턴트입니다...\n\n모든 세션과 공급자에서 AI가 어떻게 행동해야 하는지에 대한 절대적인 규칙을 정의할 수 있습니다.',
|
||||
'settings.behavior.page.section.responseStyle': '응답 스타일',
|
||||
'settings.behavior.page.responseStyle.tooltip': '활성화하면 이 지침이 새 대화에서 어시스턴트의 응답 방식을 안내합니다. 첫 메시지와 함께 전송되며 전역 AGENTS.md 규칙은 변경하지 않습니다.',
|
||||
|
||||
@@ -151,6 +151,11 @@ export const settingsDict = {
|
||||
'settings.behavior.page.responseStyle.tooltip': 'Jeśli jest włączone, instrukcje te określają sposób odpowiadania asystenta w każdej nowej rozmowie. Są one przesyłane z Twoją pierwszą wiadomością i nie zmieniają Twoich globalnych reguł AGENTS.md.',
|
||||
'settings.behavior.page.section.responseStyle': 'Styl odpowiedzi',
|
||||
'settings.behavior.page.section.systemPrompt': 'Globalny AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': 'Optymalizacja promptu systemowego',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': 'Optymalizuj rozmiar promptu systemowego',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': 'Optymalizuj rozmiar promptu systemowego OpenCode',
|
||||
'settings.behavior.page.systemPromptOptimization.info': 'Zmniejsza prompt systemowy o około 40% dla agentów build i plan. Inni agenci pozostają bez zmian. Może to usunąć niestandardowe definicje zastępujące build lub plan, dlatego nie włączaj tej opcji w takich przepływach pracy. Zmiana zostanie zastosowana po ponownym uruchomieniu OpenCode.',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': 'Ponowne uruchamianie OpenCode w celu zastosowania optymalizacji promptu systemowego…',
|
||||
'settings.behavior.page.title': 'Zachowanie',
|
||||
'settings.behavior.page.toast.saveFailed': 'Nie udało się zapisać zachowania',
|
||||
'settings.behavior.page.toast.saved': 'Zachowanie zostało zapisane pomyślnie',
|
||||
|
||||
@@ -546,6 +546,11 @@ export const settingsDict = {
|
||||
"settings.behavior.page.warning.title": "Regras globais são combinadas com regras do projeto",
|
||||
"settings.behavior.page.warning.description": "As alterações feitas aqui atualizam {path}. O OpenCode também inclui qualquer AGENTS.md a nível de projeto quando existir.",
|
||||
"settings.behavior.page.section.systemPrompt": "AGENTS.md global",
|
||||
"settings.behavior.page.section.systemPromptOptimization": "Otimização do prompt do sistema",
|
||||
"settings.behavior.page.systemPromptOptimization.enable": "Otimizar o tamanho do prompt do sistema",
|
||||
"settings.behavior.page.systemPromptOptimization.enableAria": "Otimizar o tamanho do prompt do sistema do OpenCode",
|
||||
"settings.behavior.page.systemPromptOptimization.info": "Reduz o prompt do sistema em cerca de 40% para os agentes build e plan. Os outros agentes não são alterados. Isso pode remover definições personalizadas que substituem build ou plan, portanto não ative nesses fluxos de trabalho. A alteração é aplicada após reiniciar o OpenCode.",
|
||||
"settings.behavior.page.systemPromptOptimization.restarting": "Reiniciando o OpenCode para aplicar a otimização do prompt do sistema…",
|
||||
"settings.behavior.page.field.systemPromptPlaceholder": "Você é um assistente de IA útil...\n\nUse este espaço para definir regras absolutas sobre como a IA deve se comportar em todas as sessões e provedores.",
|
||||
"settings.behavior.page.section.responseStyle": "Estilo de resposta",
|
||||
"settings.behavior.page.responseStyle.tooltip": "Quando habilitado, estas instruções orientam como o assistente responde em cada nova conversa. Elas são enviadas com sua primeira mensagem e não alteram suas regras globais de AGENTS.md.",
|
||||
|
||||
@@ -546,6 +546,11 @@ export const settingsDict = {
|
||||
"settings.behavior.page.warning.title": "Глобальні правила поєднуються з правилами проєкту",
|
||||
"settings.behavior.page.warning.description": "Зміни тут оновлюють {path}. OpenCode також додає правила з AGENTS.md на рівні проєкту, якщо вони існують.",
|
||||
"settings.behavior.page.section.systemPrompt": "Глобальний AGENTS.md",
|
||||
"settings.behavior.page.section.systemPromptOptimization": "Оптимізація системного промпту",
|
||||
"settings.behavior.page.systemPromptOptimization.enable": "Оптимізувати розмір системного промпту",
|
||||
"settings.behavior.page.systemPromptOptimization.enableAria": "Оптимізувати розмір системного промпту OpenCode",
|
||||
"settings.behavior.page.systemPromptOptimization.info": "Зменшує системний промпт орієнтовно на 40% для агентів build і plan. Інші агенти не змінюються. Це може видалити власні визначення, які перевизначають build або plan, тому не вмикайте цю функцію для таких робочих процесів. Зміна застосовується після перезапуску OpenCode.",
|
||||
"settings.behavior.page.systemPromptOptimization.restarting": "Перезапуск OpenCode для застосування оптимізації системного промпту…",
|
||||
"settings.behavior.page.field.systemPromptPlaceholder": "Ви — корисний AI-асистент...\n\nВикористовуйте цей простір для визначення абсолютних правил поведінки AI для всіх сесій та провайдерів.",
|
||||
"settings.behavior.page.section.responseStyle": "Стиль відповіді",
|
||||
"settings.behavior.page.responseStyle.tooltip": "Якщо увімкнено, ці інструкції задають стиль відповідей асистента в кожній новій розмові. Вони надсилаються разом із вашим першим повідомленням і не змінюють глобальні правила AGENTS.md.",
|
||||
|
||||
@@ -546,6 +546,11 @@ export const settingsDict = {
|
||||
'settings.behavior.page.warning.title': '全局规则会与项目规则合并使用',
|
||||
'settings.behavior.page.warning.description': '此处所做的更改会更新 {path}。当项目级 AGENTS.md 存在时,OpenCode 也会包含它。',
|
||||
'settings.behavior.page.section.systemPrompt': '全局 AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': '系统提示词优化',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': '优化系统提示词大小',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': '优化 OpenCode 系统提示词大小',
|
||||
'settings.behavior.page.systemPromptOptimization.info': '预计可将 build 和 plan 代理的系统提示词缩减约 40%,其他代理不会改变。这可能会移除覆盖 build 或 plan 的自定义定义,因此此类工作流请勿启用。重启 OpenCode 后应用更改。',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': '正在重启 OpenCode 以应用系统提示词优化…',
|
||||
'settings.behavior.page.field.systemPromptPlaceholder': '你是一个有帮助的 AI 助手...\n\n使用此空间定义 AI 在所有会话和提供商中的绝对行为规则。',
|
||||
'settings.behavior.page.section.responseStyle': '回复风格',
|
||||
'settings.behavior.page.responseStyle.tooltip': '启用后,这些说明会指导助手在每个新对话中的回复方式。它们会随你的第一条消息一起发送,不会更改全局 AGENTS.md 规则。',
|
||||
|
||||
@@ -543,6 +543,11 @@
|
||||
'settings.behavior.page.warning.title': '全域規則會與專案規則合併使用',
|
||||
'settings.behavior.page.warning.description': '此處所做的變更會更新 {path}。當專案級 AGENTS.md 存在時,OpenCode 也會包含它。',
|
||||
'settings.behavior.page.section.systemPrompt': '全域 AGENTS.md',
|
||||
'settings.behavior.page.section.systemPromptOptimization': '系統提示詞最佳化',
|
||||
'settings.behavior.page.systemPromptOptimization.enable': '最佳化系統提示詞大小',
|
||||
'settings.behavior.page.systemPromptOptimization.enableAria': '最佳化 OpenCode 系統提示詞大小',
|
||||
'settings.behavior.page.systemPromptOptimization.info': '預計可將 build 和 plan 代理程式的系統提示詞縮減約 40%,其他代理程式不會變更。這可能會移除覆寫 build 或 plan 的自訂定義,因此此類工作流程請勿啟用。重新啟動 OpenCode 後套用變更。',
|
||||
'settings.behavior.page.systemPromptOptimization.restarting': '正在重新啟動 OpenCode 以套用系統提示詞最佳化…',
|
||||
'settings.behavior.page.field.systemPromptPlaceholder': '你是一個有幫助的 AI 助助理...\n\n在此設定 AI 在所有工作階段與服務提供者中都必須遵守的絕對行為規則。',
|
||||
'settings.behavior.page.section.responseStyle': '回覆風格',
|
||||
'settings.behavior.page.responseStyle.tooltip': '啟用後,這些說明會指導助理在每個新對話中的回覆方式。它們會隨你的第一則訊息一起送出,不會變更全域 AGENTS.md 規則。',
|
||||
|
||||
@@ -556,6 +556,13 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
keywords: ['server url', 'connection token', 'import link', 'host switcher', 'additional headers', 'request headers', 'cloudflare access', 'service token'],
|
||||
isAvailable: (ctx) => ctx.isDesktop,
|
||||
},
|
||||
{
|
||||
id: 'behavior.system-prompt-optimization',
|
||||
page: 'behavior',
|
||||
titleKey: 'settings.behavior.page.section.systemPromptOptimization',
|
||||
descriptionKey: 'settings.behavior.page.systemPromptOptimization.info',
|
||||
keywords: ['system prompt', 'tokens', 'context', 'optimize', 'minimal'],
|
||||
},
|
||||
{
|
||||
id: 'behavior.system-prompt',
|
||||
page: 'behavior',
|
||||
|
||||
@@ -52,6 +52,9 @@ openchamber update # Update to latest version
|
||||
When OpenChamber launches the local OpenCode server, it also registers a native
|
||||
`openchamber` agent tool for project, session, and scheduled-task orchestration.
|
||||
The tool is not injected when connecting to an external OpenCode server.
|
||||
Behavior settings can optionally inject a managed system-prompt optimizer on
|
||||
the next OpenCode restart. It is disabled by default and is not available for
|
||||
external OpenCode servers.
|
||||
|
||||
### Tunnel behavior notes
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ import { attachRealtimeProxy } from './lib/realtime-proxy.js';
|
||||
import { createRelayService } from './lib/relay/service.js';
|
||||
import { createRelayHostLock } from './lib/relay/host-lock.js';
|
||||
import { createAgentToolRuntime } from './lib/agent-tool/runtime.js';
|
||||
import { createSystemPromptRuntime } from './lib/system-prompt/runtime.js';
|
||||
import { createOpenChamberSessionService } from './lib/openchamber-sessions/routes.js';
|
||||
import { createScheduledTaskService } from './lib/scheduled-tasks/service.js';
|
||||
import { createOpenChamberControlService } from './lib/openchamber-control/service.js';
|
||||
@@ -269,6 +270,7 @@ const readCustomThemesFromDisk = (...args) => themeRuntime.readCustomThemesFromD
|
||||
|
||||
let notificationTemplateRuntime = null;
|
||||
let agentToolRuntime = null;
|
||||
let systemPromptRuntime = null;
|
||||
|
||||
const createTimeoutSignal = (...args) => notificationTemplateRuntime.createTimeoutSignal(...args);
|
||||
const formatProjectLabel = (...args) => notificationTemplateRuntime.formatProjectLabel(...args);
|
||||
@@ -1056,8 +1058,14 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
|
||||
getActiveSessionCount,
|
||||
getManagedOpenCodeEnv: async () => {
|
||||
const settings = await readSettingsFromDiskMigrated().catch(() => null);
|
||||
if (settings?.agentControlToolEnabled === false) return {};
|
||||
return agentToolRuntime?.prepareManagedOpenCodeEnv() || {};
|
||||
const managedEnv = settings?.agentControlToolEnabled === false
|
||||
? {}
|
||||
: await (agentToolRuntime?.prepareManagedOpenCodeEnv() || {});
|
||||
if (settings?.optimizeSystemPrompt !== true) return managedEnv;
|
||||
|
||||
const configContent = managedEnv.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT;
|
||||
const systemPromptEnv = await systemPromptRuntime.prepareManagedOpenCodeEnv(configContent);
|
||||
return { ...managedEnv, ...systemPromptEnv };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1240,6 +1248,11 @@ async function main(options = {}) {
|
||||
return typeof address === 'object' && address ? address.port : null;
|
||||
},
|
||||
});
|
||||
systemPromptRuntime = createSystemPromptRuntime({
|
||||
fsPromises,
|
||||
path,
|
||||
dataDir: OPENCHAMBER_DATA_DIR,
|
||||
});
|
||||
|
||||
// Pairing transports advertised to the create-device dialog. LAN reachability is
|
||||
// derived from the SERVER's actual bind (a wildcard bind → the machine's LAN IP;
|
||||
|
||||
@@ -29,6 +29,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
||||
- `packages/web/server/lib/opencode/tunnel-wiring-runtime.js`: tunnel service/routes composition runtime and active-port wiring for main server startup.
|
||||
- `packages/web/server/lib/opencode/startup-pipeline-runtime.js`: server startup tail orchestration runtime for terminal/proxy/static/start-listen flow.
|
||||
- `packages/web/server/lib/agent-tool/runtime.js`: managed OpenCode custom-tool materialization, environment injection, loopback authentication, and fixed CLI action dispatch.
|
||||
- `packages/web/server/lib/system-prompt/runtime.js`: opt-in managed OpenCode system-prompt optimizer materialization and plugin injection.
|
||||
- `packages/web/server/lib/opencode/server-utils-runtime.js`: shared server runtime utilities for OpenCode proxy wiring, OpenCode port/readiness helpers, and snapshot fetchers.
|
||||
- `packages/web/server/lib/opencode/openchamber-routes.js`: OpenChamber update and models metadata route registration.
|
||||
- `packages/web/server/lib/opencode/pwa-manifest-routes.js`: PWA manifest route registration with recent-session shortcut resolution and short-lived caching.
|
||||
|
||||
@@ -494,6 +494,9 @@ export const createSettingsHelpers = (dependencies) => {
|
||||
if (typeof candidate.agentControlToolEnabled === 'boolean') {
|
||||
result.agentControlToolEnabled = candidate.agentControlToolEnabled;
|
||||
}
|
||||
if (typeof candidate.optimizeSystemPrompt === 'boolean') {
|
||||
result.optimizeSystemPrompt = candidate.optimizeSystemPrompt;
|
||||
}
|
||||
if (typeof candidate.openCodeUpdateToastDismissedVersion === 'string') {
|
||||
const version = candidate.openCodeUpdateToastDismissedVersion.trim();
|
||||
result.openCodeUpdateToastDismissedVersion = version.slice(0, VERSION_STRING_MAX_LENGTH);
|
||||
|
||||
@@ -409,6 +409,14 @@ describe('settings helpers', () => {
|
||||
expect(helpers.sanitizeSettingsUpdate({ recentEfforts: { 'anthropic/claude-opus-4': [123, ''] } })).toEqual({});
|
||||
});
|
||||
|
||||
it('persists only boolean system prompt optimization values', () => {
|
||||
const helpers = createTestHelpersWithRealSanitizers();
|
||||
|
||||
expect(helpers.sanitizeSettingsUpdate({ optimizeSystemPrompt: true })).toEqual({ optimizeSystemPrompt: true });
|
||||
expect(helpers.sanitizeSettingsUpdate({ optimizeSystemPrompt: false })).toEqual({ optimizeSystemPrompt: false });
|
||||
expect(helpers.sanitizeSettingsUpdate({ optimizeSystemPrompt: 'true' })).toEqual({});
|
||||
});
|
||||
|
||||
it('survives a full settings.json payload containing all four previously-dropped fields (regression)', () => {
|
||||
const helpers = createTestHelpersWithRealSanitizers();
|
||||
const payload = {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Managed System Prompt Optimizer
|
||||
|
||||
## Purpose
|
||||
|
||||
This module injects an opt-in OpenCode plugin only when OpenChamber launches
|
||||
and owns the OpenCode process and `optimizeSystemPrompt` is enabled. The plugin
|
||||
replaces OpenCode's built-in behavioral/provider prompt with a short identity
|
||||
while preserving the environment, project instructions, MCP instructions,
|
||||
skills, conversation history, and separately supplied tools.
|
||||
|
||||
## Runtime flow
|
||||
|
||||
1. Settings persist `optimizeSystemPrompt` in OpenChamber's `settings.json`.
|
||||
2. The setting is applied when managed OpenCode restarts.
|
||||
3. The runtime materializes the plugin under
|
||||
`<openchamber-data-dir>/system-prompt/` and appends its `file://` URL to
|
||||
`OPENCODE_CONFIG_CONTENT` without replacing existing plugin entries.
|
||||
4. The plugin tracks the selected agent through `chat.message`. The transform
|
||||
runs only for sessions using the built-in `build` or `plan` agent.
|
||||
5. The transform locates OpenCode's environment boundary and removes only the
|
||||
preceding text. If the boundary is absent, it leaves the prompt unchanged.
|
||||
|
||||
## Limitations
|
||||
|
||||
OpenCode exposes the assembled prompt rather than structured sections. A custom
|
||||
prompt configured by overriding the `build` or `plan` agent occupies the same
|
||||
prefix as the built-in provider prompt, so the optimizer also removes that
|
||||
override. Other agents are never transformed. The setting is off by default.
|
||||
|
||||
Plan-mode restrictions and build-mode transitions are not part of the removed
|
||||
prefix. OpenCode injects those as synthetic message reminders after system
|
||||
prompt transformation and separately enforces plan restrictions through tool
|
||||
permissions.
|
||||
|
||||
The plugin is not injected for external OpenCode servers or VS Code's separate
|
||||
OpenCode lifecycle.
|
||||
@@ -0,0 +1,68 @@
|
||||
import { parse as parseJsonc } from 'jsonc-parser';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
const PROVIDER_PROMPT_BOUNDARY = 'You are powered by the model named';
|
||||
const MINIMAL_IDENTITY = 'You are OpenCode, a coding agent.';
|
||||
|
||||
const createPluginSource = () => String.raw`
|
||||
const PROVIDER_PROMPT_BOUNDARY = ${JSON.stringify(PROVIDER_PROMPT_BOUNDARY)}
|
||||
const MINIMAL_IDENTITY = ${JSON.stringify(MINIMAL_IDENTITY)}
|
||||
const optimizedSessions = new Map()
|
||||
|
||||
export const OpenChamberSystemPromptPlugin = async () => ({
|
||||
"chat.message": async (input, output) => {
|
||||
if (!input.sessionID) return
|
||||
const agent = output?.message?.agent ?? input.agent
|
||||
if (agent === "build" || agent === "plan") {
|
||||
optimizedSessions.set(input.sessionID, agent)
|
||||
return
|
||||
}
|
||||
optimizedSessions.delete(input.sessionID)
|
||||
},
|
||||
event: async ({ event }) => {
|
||||
if (event?.type === "session.deleted") optimizedSessions.delete(event.properties?.info?.id)
|
||||
},
|
||||
"experimental.chat.system.transform": async (input, output) => {
|
||||
if (!input.sessionID || !optimizedSessions.has(input.sessionID)) return
|
||||
const prompt = output.system.join("\n")
|
||||
const boundary = prompt.indexOf(PROVIDER_PROMPT_BOUNDARY)
|
||||
if (boundary < 0) return
|
||||
output.system.length = 0
|
||||
output.system.push(MINIMAL_IDENTITY + "\n\n" + prompt.slice(boundary))
|
||||
},
|
||||
})
|
||||
`;
|
||||
|
||||
const mergePluginConfig = (rawConfig, pluginUrl) => {
|
||||
const errors = [];
|
||||
const parsed = typeof rawConfig === 'string' && rawConfig.trim()
|
||||
? parseJsonc(rawConfig, errors, { allowTrailingComma: true })
|
||||
: {};
|
||||
if (errors.length > 0 || !parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error('OPENCODE_CONFIG_CONTENT must contain a valid JSON object before OpenChamber can inject its system prompt optimizer');
|
||||
}
|
||||
if (parsed.plugin !== undefined && !Array.isArray(parsed.plugin)) {
|
||||
throw new Error('OPENCODE_CONFIG_CONTENT plugin must be an array before OpenChamber can inject its system prompt optimizer');
|
||||
}
|
||||
const configured = Array.isArray(parsed.plugin) ? parsed.plugin : [];
|
||||
parsed.plugin = [
|
||||
...configured.filter((value) => value !== pluginUrl && (!Array.isArray(value) || value[0] !== pluginUrl)),
|
||||
pluginUrl,
|
||||
];
|
||||
return JSON.stringify(parsed);
|
||||
};
|
||||
|
||||
export const createSystemPromptRuntime = ({ fsPromises, path, dataDir }) => {
|
||||
const pluginDirectory = path.join(dataDir, 'system-prompt');
|
||||
const pluginPath = path.join(pluginDirectory, 'openchamber-system-prompt-plugin.js');
|
||||
|
||||
const prepareManagedOpenCodeEnv = async (rawConfig) => {
|
||||
await fsPromises.mkdir(pluginDirectory, { recursive: true });
|
||||
await fsPromises.writeFile(pluginPath, createPluginSource(), { mode: 0o600 });
|
||||
return {
|
||||
OPENCODE_CONFIG_CONTENT: mergePluginConfig(rawConfig, pathToFileURL(pluginPath).href),
|
||||
};
|
||||
};
|
||||
|
||||
return { prepareManagedOpenCodeEnv };
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { createSystemPromptRuntime } from './runtime.js';
|
||||
|
||||
const temporaryDirectories = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryDirectories.splice(0).map((directory) => fs.rm(directory, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('managed system prompt runtime', () => {
|
||||
it.each(['build', 'plan'])('materializes the optimizer for the %s agent and preserves existing plugins', async (agent) => {
|
||||
const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openchamber-system-prompt-'));
|
||||
temporaryDirectories.push(dataDir);
|
||||
const runtime = createSystemPromptRuntime({ fsPromises: fs, path, dataDir });
|
||||
|
||||
const prepared = await runtime.prepareManagedOpenCodeEnv('{ "plugin": ["file:///existing.js"], "model": "test/model" }');
|
||||
const config = JSON.parse(prepared.OPENCODE_CONFIG_CONTENT);
|
||||
const pluginPath = path.join(dataDir, 'system-prompt', 'openchamber-system-prompt-plugin.js');
|
||||
|
||||
expect(config.model).toBe('test/model');
|
||||
expect(config.plugin).toEqual(['file:///existing.js', pathToFileURL(pluginPath).href]);
|
||||
const pluginModule = await import(`${pathToFileURL(pluginPath).href}?test=${Date.now()}`);
|
||||
const hooks = await pluginModule.OpenChamberSystemPromptPlugin();
|
||||
const output = {
|
||||
system: ['Behavioral prompt\nYou are powered by the model named GPT.\n<env>kept</env>'],
|
||||
};
|
||||
await hooks['chat.message'](
|
||||
{ sessionID: 'session-1', ...(agent === 'build' ? { agent } : {}) },
|
||||
{ message: { agent } },
|
||||
);
|
||||
await hooks['experimental.chat.system.transform']({ sessionID: 'session-1' }, output);
|
||||
expect(output.system).toEqual([
|
||||
'You are OpenCode, a coding agent.\n\nYou are powered by the model named GPT.\n<env>kept</env>',
|
||||
]);
|
||||
});
|
||||
|
||||
it('leaves an unknown prompt format unchanged', async () => {
|
||||
const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openchamber-system-prompt-'));
|
||||
temporaryDirectories.push(dataDir);
|
||||
const runtime = createSystemPromptRuntime({ fsPromises: fs, path, dataDir });
|
||||
await runtime.prepareManagedOpenCodeEnv('{}');
|
||||
const pluginPath = path.join(dataDir, 'system-prompt', 'openchamber-system-prompt-plugin.js');
|
||||
const pluginModule = await import(`${pathToFileURL(pluginPath).href}?test=${Date.now()}`);
|
||||
const hooks = await pluginModule.OpenChamberSystemPromptPlugin();
|
||||
const output = { system: ['Unrecognized prompt'] };
|
||||
await hooks['chat.message']({ sessionID: 'session-1', agent: 'plan' });
|
||||
await hooks['experimental.chat.system.transform']({ sessionID: 'session-1' }, output);
|
||||
expect(output.system).toEqual(['Unrecognized prompt']);
|
||||
});
|
||||
|
||||
it('does not transform prompts for other agents', async () => {
|
||||
const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openchamber-system-prompt-'));
|
||||
temporaryDirectories.push(dataDir);
|
||||
const runtime = createSystemPromptRuntime({ fsPromises: fs, path, dataDir });
|
||||
await runtime.prepareManagedOpenCodeEnv('{}');
|
||||
const pluginPath = path.join(dataDir, 'system-prompt', 'openchamber-system-prompt-plugin.js');
|
||||
const pluginModule = await import(`${pathToFileURL(pluginPath).href}?test=${Date.now()}`);
|
||||
const hooks = await pluginModule.OpenChamberSystemPromptPlugin();
|
||||
const output = {
|
||||
system: ['Custom agent prompt\nYou are powered by the model named GPT.\n<env>kept</env>'],
|
||||
};
|
||||
|
||||
await hooks['chat.message']({ sessionID: 'session-1', agent: 'build' });
|
||||
await hooks['chat.message']({ sessionID: 'session-1', agent: 'review' });
|
||||
await hooks['experimental.chat.system.transform']({ sessionID: 'session-1' }, output);
|
||||
|
||||
expect(output.system).toEqual([
|
||||
'Custom agent prompt\nYou are powered by the model named GPT.\n<env>kept</env>',
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user