feat: session goals - server-driven goal loop with independent small-model audit (#2148)

Arm the target button in the composer and the next prompt becomes a goal:
the server keeps the session working toward it (idle tick -> small-model
audit -> continuation) until the objective is verifiably complete, blocked,
or out of budget — even with the UI closed.

Server (packages/web/server/lib/session-goal):
- event-driven loop on the global SSE hub; goal state lives in
  session.metadata.openchamber.goal (merge-safe patches, stale-write guard
  by goal id), so it survives restarts and syncs to every client for free
- the small-model audit (objective + last assistant turn only, language
  pinned to the objective) is the sole termination authority; blocked needs
  3 consecutive verdicts, audit outages tolerate one unaudited continuation
  then stop the goal as resumable-blocked
- hard stops: optional token budget, auto-continuation cap (Resume grants a
  fresh allowance), turn errors; user abort pauses the goal instead of
  blocking it, and resuming over an aborted tail nudges immediately
- token accounting as a snapshot of the latest turn (input + cache.read +
  output), goal-relative via a creation baseline and segmented across
  compactions; a compaction summary skips the audit and continues
- continuations reuse the session's own provider/model/agent/variant

UI:
- three-mode target button (arm / disarm / manage dialog), informational
  goal strip with inline pause/resume and an Evaluating indicator, sidebar
  state glyph, objective length counter (2000-char server clamp),
  read-only completed goals
- goal entry points: composer (sessions and drafts), start-new-session-
  from-answer dialog, plan implement dialog (plan content becomes the
  objective), scheduled tasks (Run as goal + budget)
- Settings -> Chat -> Goal: feature toggle + default token budget with
  three-layer parity (web server, client persistence, VS Code bridge);
  VS Code renders goal state but hides the entry points (the loop runs in
  the web server only)

Notifications: per-turn "ready" notifications are suppressed while a goal
is active; settling sends one final notification (desktop, web-push, APNs
generic titles with the session name as body) honoring the completion
toggle. Error/question/permission notifications are untouched.

Docs: user guide (session-goals) in all 9 locales + sidebar entry,
scheduled-tasks cross-reference, server module DOCUMENTATION.md.
This commit is contained in:
Bohdan Triapitsyn
2026-07-12 01:23:22 +03:00
committed by GitHub
parent 82c039117a
commit bb45164ae8
73 changed files with 3330 additions and 29 deletions
+18
View File
@@ -8,6 +8,9 @@ type AppearanceSlice = {
showReasoningTraces: boolean;
sessionRecapEnabled: boolean;
sessionSuggestionEnabled: boolean;
sessionGoalEnabled: boolean;
sessionGoalDefaultBudgetEnabled: boolean;
sessionGoalDefaultBudget: number;
collapsibleThinkingBlocks: boolean;
showDeletionDialog: boolean;
nativeNotificationsEnabled: boolean;
@@ -55,6 +58,9 @@ export const startAppearanceAutoSave = (): void => {
showReasoningTraces: useUIStore.getState().showReasoningTraces,
sessionRecapEnabled: useUIStore.getState().sessionRecapEnabled,
sessionSuggestionEnabled: useUIStore.getState().sessionSuggestionEnabled,
sessionGoalEnabled: useUIStore.getState().sessionGoalEnabled,
sessionGoalDefaultBudgetEnabled: useUIStore.getState().sessionGoalDefaultBudgetEnabled,
sessionGoalDefaultBudget: useUIStore.getState().sessionGoalDefaultBudget,
collapsibleThinkingBlocks: useUIStore.getState().collapsibleThinkingBlocks,
showDeletionDialog: useUIStore.getState().showDeletionDialog,
nativeNotificationsEnabled: useUIStore.getState().nativeNotificationsEnabled,
@@ -109,6 +115,9 @@ export const startAppearanceAutoSave = (): void => {
showReasoningTraces: state.showReasoningTraces,
sessionRecapEnabled: state.sessionRecapEnabled,
sessionSuggestionEnabled: state.sessionSuggestionEnabled,
sessionGoalEnabled: state.sessionGoalEnabled,
sessionGoalDefaultBudgetEnabled: state.sessionGoalDefaultBudgetEnabled,
sessionGoalDefaultBudget: state.sessionGoalDefaultBudget,
collapsibleThinkingBlocks: state.collapsibleThinkingBlocks,
showDeletionDialog: state.showDeletionDialog,
nativeNotificationsEnabled: state.nativeNotificationsEnabled,
@@ -149,6 +158,15 @@ export const startAppearanceAutoSave = (): void => {
if (current.sessionSuggestionEnabled !== previous.sessionSuggestionEnabled) {
diff.sessionSuggestionEnabled = current.sessionSuggestionEnabled;
}
if (current.sessionGoalEnabled !== previous.sessionGoalEnabled) {
diff.sessionGoalEnabled = current.sessionGoalEnabled;
}
if (current.sessionGoalDefaultBudgetEnabled !== previous.sessionGoalDefaultBudgetEnabled) {
diff.sessionGoalDefaultBudgetEnabled = current.sessionGoalDefaultBudgetEnabled;
}
if (current.sessionGoalDefaultBudget !== previous.sessionGoalDefaultBudget) {
diff.sessionGoalDefaultBudget = current.sessionGoalDefaultBudget;
}
if (current.collapsibleThinkingBlocks !== previous.collapsibleThinkingBlocks) {
diff.collapsibleThinkingBlocks = current.collapsibleThinkingBlocks;
}
+3
View File
@@ -119,6 +119,9 @@ export type DesktopSettings = {
smallModelUseDefault?: boolean;
sessionRecapEnabled?: boolean;
sessionSuggestionEnabled?: boolean;
sessionGoalEnabled?: boolean;
sessionGoalDefaultBudgetEnabled?: boolean;
sessionGoalDefaultBudget?: number;
smallModelOverride?: string; // format: "provider/model"
defaultGitIdentityId?: string; // ''/undefined = unset, 'global' or profile id
openInAppId?: string;
@@ -1760,6 +1760,12 @@ export const settingsDict = {
'settings.openchamber.visual.field.sessionRecapAria': 'Generate a recap after the agent finishes',
'settings.openchamber.visual.field.sessionSuggestion': 'Generate Next User Message Suggestion',
'settings.openchamber.visual.field.sessionSuggestionAria': 'Generate a suggested next user message after the agent finishes',
'settings.openchamber.visual.field.sessionGoal': 'Enable Session Goals',
'settings.openchamber.visual.field.sessionGoalAria': 'Keep the session working toward a goal automatically',
'settings.openchamber.visual.goal.sectionTitle': 'Goal',
'settings.openchamber.visual.goal.budgetLabel': 'Default token budget',
'settings.openchamber.visual.goal.budgetAria': 'Apply a default token budget to new goals',
'settings.openchamber.visual.goal.description': 'Arm the target button in the composer and the next message becomes a goal: the agent keeps working toward it automatically, audited by the small model, even while you are away.',
'settings.openchamber.visual.field.showReasoningTracesAria': 'Show reasoning traces',
'settings.openchamber.visual.field.showReasoningTraces': 'Show Reasoning Traces',
'settings.openchamber.visual.field.collapsibleThinkingBlocksAria': 'Enable collapsible reasoning blocks',
+37
View File
@@ -292,6 +292,10 @@ export const dict = {
'sessions.scheduledTasks.editor.prompt.placeholder': 'Summarize open tasks and propose next actions',
'sessions.scheduledTasks.editor.enabled.aria': 'Enable task',
'sessions.scheduledTasks.editor.enabled.label': 'Enabled',
'sessions.scheduledTasks.editor.goal.label': 'Run as goal',
'sessions.scheduledTasks.editor.goal.aria': 'Run this task as a goal the agent pursues to completion',
'sessions.scheduledTasks.editor.goal.budgetLabel': 'Token budget',
'sessions.scheduledTasks.editor.goal.budgetAria': 'Limit the goal to a token budget',
'sessions.scheduledTasks.editor.actions.closeAria': 'Close',
'sessions.scheduledTasks.editor.actions.cancel': 'Cancel',
'sessions.scheduledTasks.editor.actions.save': 'Save',
@@ -1411,6 +1415,39 @@ export const dict = {
'header.actions.terminalPanelWithShortcut': 'Terminal panel ({shortcut})',
'chat.recap.aria': 'Session recap',
'chat.recap.label': 'Recap:',
'chat.goal.dialog.titleCreate': 'Set Session Goal',
'chat.goal.dialog.titleManage': 'Session Goal',
'chat.goal.dialog.objectiveLabel': 'Objective',
'chat.goal.dialog.objectivePlaceholder': 'Describe the end state the agent should reach and verify…',
'chat.goal.dialog.budgetLabel': 'Token budget',
'chat.goal.status.active': 'Active',
'chat.goal.status.evaluating': 'Evaluating…',
'chat.goal.status.paused': 'Paused',
'chat.goal.status.blocked': 'Blocked',
'chat.goal.status.budgetLimited': 'Budget reached',
'chat.goal.status.complete': 'Complete',
'chat.goal.usage.tokens': '{used} tokens',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} tokens',
'chat.goal.usage.turns': '{turns} continuations',
'chat.goal.action.pause': 'Pause',
'chat.goal.action.resume': 'Resume',
'chat.goal.action.markComplete': 'Mark complete',
'chat.goal.action.clear': 'Remove goal',
'chat.goal.action.cancel': 'Cancel',
'chat.goal.action.save': 'Save goal',
'chat.goal.action.start': 'Start goal',
'chat.goal.toast.actionFailed': 'Goal update failed',
'chat.goal.row.aria': 'Session goal — open details',
'chat.goal.button.createAria': 'Set a session goal',
'chat.goal.button.manageAria': 'Manage session goal',
'chat.goal.button.armAria': 'Start a goal with the next message',
'chat.goal.counter.aria': 'Goal objective length limit',
'chat.goal.button.disarmAria': 'Goal armed — tap to disarm',
'chat.goal.button.cancelAria': 'Goal running — tap to cancel it',
'chat.goal.cancelDialog.title': 'Cancel this goal?',
'chat.goal.cancelDialog.description': 'The agent will stop working toward this goal automatically.',
'chat.goal.cancelDialog.keep': 'Keep goal',
'chat.goal.cancelDialog.confirm': 'Cancel goal',
'chat.suggestion.applyAria': 'Use suggested message',
'chat.suggestion.dismissAria': 'Dismiss suggestion',
'header.actions.toggleTerminalPanelAria': 'Toggle terminal panel',
@@ -1727,6 +1727,12 @@ export const settingsDict = {
"settings.openchamber.visual.field.sessionRecapAria": "Generar un resumen cuando el agente termina",
"settings.openchamber.visual.field.sessionSuggestion": "Generar sugerencia del próximo mensaje del usuario",
"settings.openchamber.visual.field.sessionSuggestionAria": "Generar un próximo mensaje sugerido del usuario cuando el agente termina",
"settings.openchamber.visual.field.sessionGoal": "Habilitar objetivos de sesión",
"settings.openchamber.visual.field.sessionGoalAria": "Mantener la sesión trabajando automáticamente hacia un objetivo",
"settings.openchamber.visual.goal.sectionTitle": "Objetivo",
"settings.openchamber.visual.goal.budgetLabel": "Presupuesto de tokens predeterminado",
"settings.openchamber.visual.goal.budgetAria": "Aplicar un presupuesto de tokens predeterminado a los nuevos objetivos",
"settings.openchamber.visual.goal.description": "Activa el botón de diana en el compositor y el próximo mensaje se convierte en un objetivo: el agente sigue trabajando hacia él automáticamente, auditado por el modelo pequeño, incluso mientras no estás.",
"settings.openchamber.visual.field.showReasoningTracesAria": "Mostrar rastros de razonamiento",
"settings.openchamber.visual.field.showReasoningTraces": "Mostrar trazas de razonamiento",
"settings.openchamber.visual.field.collapsibleThinkingBlocksAria": "Habilitar bloques de razonamiento colapsables",
+37
View File
@@ -293,6 +293,10 @@ export const dict: Record<I18nKey, string> = {
"sessions.scheduledTasks.editor.prompt.placeholder": "Resumir tareas abiertas y proponer próximas acciones",
"sessions.scheduledTasks.editor.enabled.aria": "Habilitar tarea",
"sessions.scheduledTasks.editor.enabled.label": "Habilitado",
"sessions.scheduledTasks.editor.goal.label": "Ejecutar como objetivo",
"sessions.scheduledTasks.editor.goal.aria": "Ejecutar esta tarea como un objetivo que el agente persigue hasta completarlo",
"sessions.scheduledTasks.editor.goal.budgetLabel": "Presupuesto de tokens",
"sessions.scheduledTasks.editor.goal.budgetAria": "Limitar el objetivo a un presupuesto de tokens",
"sessions.scheduledTasks.editor.actions.closeAria": "Cerrar",
"sessions.scheduledTasks.editor.actions.cancel": "Cancelar",
"sessions.scheduledTasks.editor.actions.save": "Guardar",
@@ -1389,6 +1393,39 @@ export const dict: Record<I18nKey, string> = {
"header.actions.terminalPanelWithShortcut": "Panel de terminal ({shortcut})",
"chat.recap.aria": "Resumen de la sesión",
"chat.recap.label": "Resumen:",
"chat.goal.dialog.titleCreate": "Definir objetivo de sesión",
"chat.goal.dialog.titleManage": "Objetivo de sesión",
"chat.goal.dialog.objectiveLabel": "Objetivo",
"chat.goal.dialog.objectivePlaceholder": "Describe el estado final que el agente debe alcanzar y verificar…",
"chat.goal.dialog.budgetLabel": "Presupuesto de tokens",
"chat.goal.status.active": "Activo",
"chat.goal.status.evaluating": "Evaluando…",
"chat.goal.status.paused": "En pausa",
"chat.goal.status.blocked": "Bloqueado",
"chat.goal.status.budgetLimited": "Presupuesto alcanzado",
"chat.goal.status.complete": "Completado",
"chat.goal.usage.tokens": "{used} tokens",
"chat.goal.usage.tokensWithBudget": "{used}/{budget} tokens",
"chat.goal.usage.turns": "{turns} continuaciones",
"chat.goal.action.pause": "Pausar",
"chat.goal.action.resume": "Reanudar",
"chat.goal.action.markComplete": "Marcar como completado",
"chat.goal.action.clear": "Eliminar objetivo",
"chat.goal.action.cancel": "Cancelar",
"chat.goal.action.save": "Guardar objetivo",
"chat.goal.action.start": "Iniciar objetivo",
"chat.goal.toast.actionFailed": "No se pudo actualizar el objetivo",
"chat.goal.row.aria": "Objetivo de sesión — abrir detalles",
"chat.goal.button.createAria": "Definir un objetivo de sesión",
"chat.goal.button.manageAria": "Gestionar el objetivo de sesión",
"chat.goal.button.armAria": "Iniciar un objetivo con el próximo mensaje",
"chat.goal.counter.aria": "Límite de longitud del objetivo",
"chat.goal.button.disarmAria": "Objetivo armado — toca para desactivarlo",
"chat.goal.button.cancelAria": "Objetivo en curso — toca para cancelarlo",
"chat.goal.cancelDialog.title": "¿Cancelar este objetivo?",
"chat.goal.cancelDialog.description": "El agente dejará de trabajar automáticamente hacia este objetivo.",
"chat.goal.cancelDialog.keep": "Mantener objetivo",
"chat.goal.cancelDialog.confirm": "Cancelar objetivo",
"chat.suggestion.applyAria": "Usar mensaje sugerido",
"chat.suggestion.dismissAria": "Descartar sugerencia",
"header.actions.toggleTerminalPanelAria": "Mostrar u ocultar panel de terminal",
@@ -1639,6 +1639,12 @@ export const settingsDict = {
'settings.openchamber.visual.field.sessionRecapAria': "Générer un récapitulatif quand l'agent termine",
'settings.openchamber.visual.field.sessionSuggestion': 'Générer une suggestion de prochain message utilisateur',
'settings.openchamber.visual.field.sessionSuggestionAria': "Générer un prochain message utilisateur suggéré quand l'agent termine",
'settings.openchamber.visual.field.sessionGoal': 'Activer les objectifs de session',
'settings.openchamber.visual.field.sessionGoalAria': "Faire travailler la session automatiquement vers un objectif",
'settings.openchamber.visual.goal.sectionTitle': 'Objectif',
'settings.openchamber.visual.goal.budgetLabel': 'Budget de tokens par défaut',
'settings.openchamber.visual.goal.budgetAria': 'Appliquer un budget de tokens par défaut aux nouveaux objectifs',
'settings.openchamber.visual.goal.description': 'Armez le bouton cible du composeur et le prochain message devient un objectif : l\'agent continue d\'y travailler automatiquement, audité par le petit modèle, même en votre absence.',
'settings.openchamber.visual.field.showReasoningTracesAria': 'Afficher les traces de raisonnement',
'settings.openchamber.visual.field.showReasoningTraces': 'Afficher les traces de raisonnement',
'settings.openchamber.visual.field.collapsibleThinkingBlocksAria': 'Activer les blocs de raisonnement pliables',
+37
View File
@@ -137,6 +137,10 @@ export const dict = {
'sessions.scheduledTasks.editor.prompt.placeholder': 'Résumer les tâches ouvertes et proposer les prochaines actions',
'sessions.scheduledTasks.editor.enabled.aria': 'Activer la tâche',
'sessions.scheduledTasks.editor.enabled.label': 'Activé',
'sessions.scheduledTasks.editor.goal.label': 'Exécuter comme objectif',
'sessions.scheduledTasks.editor.goal.aria': 'Exécuter cette tâche comme un objectif que l\'agent poursuit jusqu\'au bout',
'sessions.scheduledTasks.editor.goal.budgetLabel': 'Budget de tokens',
'sessions.scheduledTasks.editor.goal.budgetAria': 'Limiter lobjectif à un budget de tokens',
'sessions.scheduledTasks.editor.actions.closeAria': 'Fermer',
'sessions.scheduledTasks.editor.actions.cancel': 'Annuler',
'sessions.scheduledTasks.editor.actions.save': 'Sauvegarder',
@@ -1232,6 +1236,39 @@ export const dict = {
'header.actions.terminalPanelWithShortcut': 'Panneau à bornes ({shortcut})',
'chat.recap.aria': 'Récapitulatif de la session',
'chat.recap.label': 'Récap :',
'chat.goal.dialog.titleCreate': 'Définir un objectif de session',
'chat.goal.dialog.titleManage': 'Objectif de session',
'chat.goal.dialog.objectiveLabel': 'Objectif',
'chat.goal.dialog.objectivePlaceholder': 'Décrivez l\'état final que l\'agent doit atteindre et vérifier…',
'chat.goal.dialog.budgetLabel': 'Budget de tokens',
'chat.goal.status.active': 'Actif',
'chat.goal.status.evaluating': 'Évaluation…',
'chat.goal.status.paused': 'En pause',
'chat.goal.status.blocked': 'Bloqué',
'chat.goal.status.budgetLimited': 'Budget atteint',
'chat.goal.status.complete': 'Terminé',
'chat.goal.usage.tokens': '{used} tokens',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} tokens',
'chat.goal.usage.turns': '{turns} continuations',
'chat.goal.action.pause': 'Mettre en pause',
'chat.goal.action.resume': 'Reprendre',
'chat.goal.action.markComplete': 'Marquer comme terminé',
'chat.goal.action.clear': 'Supprimer l\'objectif',
'chat.goal.action.cancel': 'Annuler',
'chat.goal.action.save': 'Enregistrer l\'objectif',
'chat.goal.action.start': 'Lancer l\'objectif',
'chat.goal.toast.actionFailed': 'Échec de la mise à jour de l\'objectif',
'chat.goal.row.aria': 'Objectif de session — ouvrir les détails',
'chat.goal.button.createAria': 'Définir un objectif de session',
'chat.goal.button.manageAria': 'Gérer l\'objectif de session',
'chat.goal.button.armAria': 'Démarrer un objectif avec le prochain message',
"chat.goal.counter.aria": "Limite de longueur de l'objectif",
'chat.goal.button.disarmAria': 'Objectif armé — touchez pour désarmer',
'chat.goal.button.cancelAria': 'Objectif en cours — touchez pour lannuler',
'chat.goal.cancelDialog.title': 'Annuler cet objectif ?',
'chat.goal.cancelDialog.description': 'L\'agent cessera de travailler automatiquement vers cet objectif.',
'chat.goal.cancelDialog.keep': 'Garder l\'objectif',
'chat.goal.cancelDialog.confirm': 'Annuler l\'objectif',
'chat.suggestion.applyAria': 'Utiliser le message suggéré',
'chat.suggestion.dismissAria': 'Ignorer la suggestion',
'header.actions.toggleTerminalPanelAria': 'Basculer le panneau à bornes',
@@ -1760,6 +1760,12 @@ export const settingsDict = {
'settings.openchamber.visual.field.sessionRecapAria': 'エージェントの完了後に要約を生成します',
'settings.openchamber.visual.field.sessionSuggestion': '次のユーザーメッセージの提案を生成',
'settings.openchamber.visual.field.sessionSuggestionAria': 'エージェントの完了後に次のユーザーメッセージの提案を生成します',
'settings.openchamber.visual.field.sessionGoal': 'セッションゴールを有効化',
'settings.openchamber.visual.field.sessionGoalAria': 'セッションが自動的にゴールに向かって作業を続けます',
'settings.openchamber.visual.goal.sectionTitle': 'ゴール',
'settings.openchamber.visual.goal.budgetLabel': 'デフォルトのトークン予算',
'settings.openchamber.visual.goal.budgetAria': '新しいゴールにデフォルトのトークン予算を適用',
'settings.openchamber.visual.goal.description': 'コンポーザーのターゲットボタンを有効にすると、次のメッセージがゴールになります。エージェントは小型モデルの監査のもと、離席中でも自動的に作業を続けます。',
'settings.openchamber.visual.field.showReasoningTracesAria': '推論トレースを表示',
'settings.openchamber.visual.field.showReasoningTraces': '推論トレースを表示',
'settings.openchamber.visual.field.collapsibleThinkingBlocksAria': '折りたたみ可能な推論ブロックを有効化',
+37
View File
@@ -293,6 +293,10 @@ export const dict: Record<I18nKey, string> = {
'sessions.scheduledTasks.editor.prompt.placeholder': '未完了のタスクを要約し次のアクションを提案',
'sessions.scheduledTasks.editor.enabled.aria': 'タスクを有効にする',
'sessions.scheduledTasks.editor.enabled.label': '有効',
'sessions.scheduledTasks.editor.goal.label': 'ゴールとして実行',
'sessions.scheduledTasks.editor.goal.aria': 'このタスクをエージェントが完了まで追求するゴールとして実行します',
'sessions.scheduledTasks.editor.goal.budgetLabel': 'トークン予算',
'sessions.scheduledTasks.editor.goal.budgetAria': 'ゴールをトークン予算で制限します',
'sessions.scheduledTasks.editor.actions.closeAria': '閉じる',
'sessions.scheduledTasks.editor.actions.cancel': 'キャンセル',
'sessions.scheduledTasks.editor.actions.save': '保存',
@@ -1407,6 +1411,39 @@ export const dict: Record<I18nKey, string> = {
'header.actions.terminalPanelWithShortcut': 'ターミナルパネル({shortcut}',
'chat.recap.aria': 'セッションの要約',
'chat.recap.label': '要約:',
'chat.goal.dialog.titleCreate': 'セッションゴールを設定',
'chat.goal.dialog.titleManage': 'セッションゴール',
'chat.goal.dialog.objectiveLabel': '目標',
'chat.goal.dialog.objectivePlaceholder': 'エージェントが到達して検証すべき最終状態を記述してください…',
'chat.goal.dialog.budgetLabel': 'トークン予算',
'chat.goal.status.active': '進行中',
'chat.goal.status.evaluating': '評価中…',
'chat.goal.status.paused': '一時停止',
'chat.goal.status.blocked': 'ブロック',
'chat.goal.status.budgetLimited': '予算上限に到達',
'chat.goal.status.complete': '完了',
'chat.goal.usage.tokens': '{used} トークン',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} トークン',
'chat.goal.usage.turns': '継続 {turns} 回',
'chat.goal.action.pause': '一時停止',
'chat.goal.action.resume': '再開',
'chat.goal.action.markComplete': '完了にする',
'chat.goal.action.clear': 'ゴールを削除',
'chat.goal.action.cancel': 'キャンセル',
'chat.goal.action.save': 'ゴールを保存',
'chat.goal.action.start': 'ゴールを開始',
'chat.goal.toast.actionFailed': 'ゴールの更新に失敗しました',
'chat.goal.row.aria': 'セッションゴール — 詳細を開く',
'chat.goal.button.createAria': 'セッションゴールを設定',
'chat.goal.button.manageAria': 'セッションゴールを管理',
'chat.goal.button.armAria': '次のメッセージでゴールを開始',
'chat.goal.counter.aria': 'ゴール目標の文字数上限',
'chat.goal.button.disarmAria': 'ゴール待機中 — タップで解除',
'chat.goal.button.cancelAria': 'ゴール実行中 — タップでキャンセル',
'chat.goal.cancelDialog.title': 'このゴールをキャンセルしますか?',
'chat.goal.cancelDialog.description': 'エージェントはこのゴールへの自動作業を停止します。',
'chat.goal.cancelDialog.keep': 'ゴールを維持',
'chat.goal.cancelDialog.confirm': 'ゴールをキャンセル',
'chat.suggestion.applyAria': '提案されたメッセージを使用',
'chat.suggestion.dismissAria': '提案を閉じる',
'header.actions.toggleTerminalPanelAria': 'ターミナルパネルの切り替え',
@@ -1727,6 +1727,12 @@ export const settingsDict = {
'settings.openchamber.visual.field.sessionRecapAria': '에이전트가 완료되면 요약을 생성합니다',
'settings.openchamber.visual.field.sessionSuggestion': '다음 사용자 메시지 제안 생성',
'settings.openchamber.visual.field.sessionSuggestionAria': '에이전트가 완료되면 다음 사용자 메시지 제안을 생성합니다',
'settings.openchamber.visual.field.sessionGoal': '세션 목표 사용',
'settings.openchamber.visual.field.sessionGoalAria': '세션이 목표를 향해 자동으로 계속 작업하도록 합니다',
'settings.openchamber.visual.goal.sectionTitle': '목표',
'settings.openchamber.visual.goal.budgetLabel': '기본 토큰 예산',
'settings.openchamber.visual.goal.budgetAria': '새 목표에 기본 토큰 예산 적용',
'settings.openchamber.visual.goal.description': '컴포저의 타깃 버튼을 켜면 다음 메시지가 목표가 됩니다. 에이전트는 소형 모델의 감사를 받으며 자리를 비운 동안에도 자동으로 계속 작업합니다.',
'settings.openchamber.visual.field.showReasoningTracesAria': 'Reasoning trace 표시',
'settings.openchamber.visual.field.showReasoningTraces': 'Reasoning Trace 표시',
'settings.openchamber.visual.field.collapsibleThinkingBlocksAria': '접을 수 있는 추론 블록 활성화',
+37
View File
@@ -293,6 +293,10 @@ export const dict: Record<I18nKey, string> = {
'sessions.scheduledTasks.editor.prompt.placeholder': '열린 작업을 요약하고 다음 액션을 제안하세요',
'sessions.scheduledTasks.editor.enabled.aria': '작업 활성화',
'sessions.scheduledTasks.editor.enabled.label': '활성화됨',
'sessions.scheduledTasks.editor.goal.label': '목표로 실행',
'sessions.scheduledTasks.editor.goal.aria': '이 작업을 에이전트가 완료까지 추진하는 목표로 실행합니다',
'sessions.scheduledTasks.editor.goal.budgetLabel': '토큰 예산',
'sessions.scheduledTasks.editor.goal.budgetAria': '목표를 토큰 예산으로 제한합니다',
'sessions.scheduledTasks.editor.actions.closeAria': '닫기',
'sessions.scheduledTasks.editor.actions.cancel': '취소',
'sessions.scheduledTasks.editor.actions.save': '저장',
@@ -1413,6 +1417,39 @@ export const dict: Record<I18nKey, string> = {
'header.actions.terminalPanelWithShortcut': '터미널 패널 ({shortcut})',
'chat.recap.aria': '세션 요약',
'chat.recap.label': '요약:',
'chat.goal.dialog.titleCreate': '세션 목표 설정',
'chat.goal.dialog.titleManage': '세션 목표',
'chat.goal.dialog.objectiveLabel': '목표',
'chat.goal.dialog.objectivePlaceholder': '에이전트가 도달하고 검증해야 할 최종 상태를 설명하세요…',
'chat.goal.dialog.budgetLabel': '토큰 예산',
'chat.goal.status.active': '진행 중',
'chat.goal.status.evaluating': '평가 중…',
'chat.goal.status.paused': '일시 중지됨',
'chat.goal.status.blocked': '차단됨',
'chat.goal.status.budgetLimited': '예산 도달',
'chat.goal.status.complete': '완료됨',
'chat.goal.usage.tokens': '{used} 토큰',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} 토큰',
'chat.goal.usage.turns': '{turns}회 계속',
'chat.goal.action.pause': '일시 중지',
'chat.goal.action.resume': '재개',
'chat.goal.action.markComplete': '완료로 표시',
'chat.goal.action.clear': '목표 제거',
'chat.goal.action.cancel': '취소',
'chat.goal.action.save': '목표 저장',
'chat.goal.action.start': '목표 시작',
'chat.goal.toast.actionFailed': '목표 업데이트에 실패했습니다',
'chat.goal.row.aria': '세션 목표 — 세부 정보 열기',
'chat.goal.button.createAria': '세션 목표 설정',
'chat.goal.button.manageAria': '세션 목표 관리',
'chat.goal.button.armAria': '다음 메시지로 목표 시작',
'chat.goal.counter.aria': '목표 길이 제한',
'chat.goal.button.disarmAria': '목표 대기 중 — 탭하여 해제',
'chat.goal.button.cancelAria': '목표 진행 중 — 탭하여 취소',
'chat.goal.cancelDialog.title': '이 목표를 취소할까요?',
'chat.goal.cancelDialog.description': '에이전트가 이 목표를 향한 자동 작업을 중단합니다.',
'chat.goal.cancelDialog.keep': '목표 유지',
'chat.goal.cancelDialog.confirm': '목표 취소',
'chat.suggestion.applyAria': '제안된 메시지 사용',
'chat.suggestion.dismissAria': '제안 닫기',
'header.actions.toggleTerminalPanelAria': '토글 터미널 패널',
@@ -984,6 +984,12 @@ export const settingsDict = {
'settings.openchamber.visual.field.sessionRecapAria': 'Generuj podsumowanie po zakończeniu pracy agenta',
'settings.openchamber.visual.field.sessionSuggestion': 'Generuj sugestię następnej wiadomości użytkownika',
'settings.openchamber.visual.field.sessionSuggestionAria': 'Generuj sugerowaną następną wiadomość użytkownika po zakończeniu pracy agenta',
'settings.openchamber.visual.field.sessionGoal': 'Włącz cele sesji',
'settings.openchamber.visual.field.sessionGoalAria': 'Utrzymuj automatyczną pracę sesji w kierunku celu',
'settings.openchamber.visual.goal.sectionTitle': 'Cel',
'settings.openchamber.visual.goal.budgetLabel': 'Domyślny budżet tokenów',
'settings.openchamber.visual.goal.budgetAria': 'Stosuj domyślny budżet tokenów do nowych celów',
'settings.openchamber.visual.goal.description': 'Uzbrój przycisk celu w kompozytorze, a następna wiadomość stanie się celem: agent będzie nad nim automatycznie pracować, audytowany przez mały model, nawet pod twoją nieobecność.',
'settings.openchamber.visual.field.showReasoningTraces': 'Pokaż ślady rozumowania',
'settings.openchamber.visual.field.showReasoningTracesAria': 'Pokaż ślady rozumowania',
'settings.openchamber.visual.field.collapsibleThinkingBlocks': 'Włącz zwijalne bloki rozumowania',
+37
View File
@@ -381,6 +381,10 @@ export const dict: Record<I18nKey, string> = {
'sessions.scheduledTasks.editor.prompt.placeholder': 'Podsumuj otwarte zadania i zaproponuj następne działania',
'sessions.scheduledTasks.editor.enabled.aria': 'Włącz zadanie',
'sessions.scheduledTasks.editor.enabled.label': 'Włączone',
'sessions.scheduledTasks.editor.goal.label': 'Uruchom jako cel',
'sessions.scheduledTasks.editor.goal.aria': 'Uruchom to zadanie jako cel, do którego agent dąży aż do ukończenia',
'sessions.scheduledTasks.editor.goal.budgetLabel': 'Budżet tokenów',
'sessions.scheduledTasks.editor.goal.budgetAria': 'Ogranicz cel budżetem tokenów',
'sessions.scheduledTasks.editor.actions.closeAria': 'Zamknij',
'sessions.scheduledTasks.editor.actions.cancel': 'Anuluj',
'sessions.scheduledTasks.editor.actions.save': 'Zapisz',
@@ -2093,6 +2097,39 @@ export const dict: Record<I18nKey, string> = {
'header.actions.terminalPanelWithShortcut': 'Panel terminala ({shortcut})',
'chat.recap.aria': 'Podsumowanie sesji',
'chat.recap.label': 'Podsumowanie:',
'chat.goal.dialog.titleCreate': 'Ustaw cel sesji',
'chat.goal.dialog.titleManage': 'Cel sesji',
'chat.goal.dialog.objectiveLabel': 'Cel',
'chat.goal.dialog.objectivePlaceholder': 'Opisz stan końcowy, który agent ma osiągnąć i zweryfikować…',
'chat.goal.dialog.budgetLabel': 'Budżet tokenów',
'chat.goal.status.active': 'Aktywny',
'chat.goal.status.evaluating': 'Ocenianie…',
'chat.goal.status.paused': 'Wstrzymany',
'chat.goal.status.blocked': 'Zablokowany',
'chat.goal.status.budgetLimited': 'Budżet wyczerpany',
'chat.goal.status.complete': 'Ukończony',
'chat.goal.usage.tokens': '{used} tokenów',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} tokenów',
'chat.goal.usage.turns': 'kontynuacje: {turns}',
'chat.goal.action.pause': 'Wstrzymaj',
'chat.goal.action.resume': 'Wznów',
'chat.goal.action.markComplete': 'Oznacz jako ukończony',
'chat.goal.action.clear': 'Usuń cel',
'chat.goal.action.cancel': 'Anuluj',
'chat.goal.action.save': 'Zapisz cel',
'chat.goal.action.start': 'Rozpocznij cel',
'chat.goal.toast.actionFailed': 'Nie udało się zaktualizować celu',
'chat.goal.row.aria': 'Cel sesji — otwórz szczegóły',
'chat.goal.button.createAria': 'Ustaw cel sesji',
'chat.goal.button.manageAria': 'Zarządzaj celem sesji',
'chat.goal.button.armAria': 'Rozpocznij cel następną wiadomością',
'chat.goal.counter.aria': 'Limit długości celu',
'chat.goal.button.disarmAria': 'Cel uzbrojony — dotknij, aby rozbroić',
'chat.goal.button.cancelAria': 'Cel w toku — dotknij, aby anulować',
'chat.goal.cancelDialog.title': 'Anulować ten cel?',
'chat.goal.cancelDialog.description': 'Agent przestanie automatycznie pracować nad tym celem.',
'chat.goal.cancelDialog.keep': 'Zachowaj cel',
'chat.goal.cancelDialog.confirm': 'Anuluj cel',
'chat.suggestion.applyAria': 'Użyj sugerowanej wiadomości',
'chat.suggestion.dismissAria': 'Odrzuć sugestię',
'header.actions.toggleRightSidebarAria': 'Przełącz prawy panel boczny',
@@ -1727,6 +1727,12 @@ export const settingsDict = {
"settings.openchamber.visual.field.sessionRecapAria": "Gerar um resumo quando o agente termina",
"settings.openchamber.visual.field.sessionSuggestion": "Gerar sugestão da próxima mensagem do usuário",
"settings.openchamber.visual.field.sessionSuggestionAria": "Gerar uma próxima mensagem sugerida do usuário quando o agente termina",
"settings.openchamber.visual.field.sessionGoal": "Habilitar objetivos de sessão",
"settings.openchamber.visual.field.sessionGoalAria": "Manter a sessão trabalhando automaticamente em direção a um objetivo",
"settings.openchamber.visual.goal.sectionTitle": "Objetivo",
"settings.openchamber.visual.goal.budgetLabel": "Orçamento de tokens padrão",
"settings.openchamber.visual.goal.budgetAria": "Aplicar um orçamento de tokens padrão a novos objetivos",
"settings.openchamber.visual.goal.description": "Arme o botão de alvo no compositor e a próxima mensagem vira um objetivo: o agente continua trabalhando nele automaticamente, auditado pelo modelo pequeno, mesmo enquanto você está ausente.",
"settings.openchamber.visual.field.showReasoningTracesAria": "Mostrar rastros de raciocínio",
"settings.openchamber.visual.field.showReasoningTraces": "Mostrar rastros de raciocínio",
"settings.openchamber.visual.field.collapsibleThinkingBlocksAria": "Habilitar blocos de raciocínio recolhíveis",
@@ -293,6 +293,10 @@ export const dict: Record<I18nKey, string> = {
"sessions.scheduledTasks.editor.prompt.placeholder": "Resumir tarefas abertas e propor próximas ações",
"sessions.scheduledTasks.editor.enabled.aria": "Ativar tarefa",
"sessions.scheduledTasks.editor.enabled.label": "Ativado",
"sessions.scheduledTasks.editor.goal.label": "Executar como objetivo",
"sessions.scheduledTasks.editor.goal.aria": "Executar esta tarefa como um objetivo que o agente persegue até concluir",
"sessions.scheduledTasks.editor.goal.budgetLabel": "Orçamento de tokens",
"sessions.scheduledTasks.editor.goal.budgetAria": "Limitar o objetivo a um orçamento de tokens",
"sessions.scheduledTasks.editor.actions.closeAria": "Fechar",
"sessions.scheduledTasks.editor.actions.cancel": "Cancelar",
"sessions.scheduledTasks.editor.actions.save": "Salvar",
@@ -1389,6 +1393,39 @@ export const dict: Record<I18nKey, string> = {
"header.actions.terminalPanelWithShortcut": "Painel de terminal ({shortcut})",
"chat.recap.aria": "Resumo da sessão",
"chat.recap.label": "Resumo:",
"chat.goal.dialog.titleCreate": "Definir objetivo da sessão",
"chat.goal.dialog.titleManage": "Objetivo da sessão",
"chat.goal.dialog.objectiveLabel": "Objetivo",
"chat.goal.dialog.objectivePlaceholder": "Descreva o estado final que o agente deve alcançar e verificar…",
"chat.goal.dialog.budgetLabel": "Orçamento de tokens",
"chat.goal.status.active": "Ativo",
"chat.goal.status.evaluating": "Avaliando…",
"chat.goal.status.paused": "Pausado",
"chat.goal.status.blocked": "Bloqueado",
"chat.goal.status.budgetLimited": "Orçamento atingido",
"chat.goal.status.complete": "Concluído",
"chat.goal.usage.tokens": "{used} tokens",
"chat.goal.usage.tokensWithBudget": "{used}/{budget} tokens",
"chat.goal.usage.turns": "{turns} continuações",
"chat.goal.action.pause": "Pausar",
"chat.goal.action.resume": "Retomar",
"chat.goal.action.markComplete": "Marcar como concluído",
"chat.goal.action.clear": "Remover objetivo",
"chat.goal.action.cancel": "Cancelar",
"chat.goal.action.save": "Salvar objetivo",
"chat.goal.action.start": "Iniciar objetivo",
"chat.goal.toast.actionFailed": "Falha ao atualizar o objetivo",
"chat.goal.row.aria": "Objetivo da sessão — abrir detalhes",
"chat.goal.button.createAria": "Definir um objetivo da sessão",
"chat.goal.button.manageAria": "Gerenciar objetivo da sessão",
"chat.goal.button.armAria": "Iniciar um objetivo com a próxima mensagem",
"chat.goal.counter.aria": "Limite de comprimento do objetivo",
"chat.goal.button.disarmAria": "Objetivo armado — toque para desarmar",
"chat.goal.button.cancelAria": "Objetivo em andamento — toque para cancelar",
"chat.goal.cancelDialog.title": "Cancelar este objetivo?",
"chat.goal.cancelDialog.description": "O agente deixará de trabalhar automaticamente neste objetivo.",
"chat.goal.cancelDialog.keep": "Manter objetivo",
"chat.goal.cancelDialog.confirm": "Cancelar objetivo",
"chat.suggestion.applyAria": "Usar mensagem sugerida",
"chat.suggestion.dismissAria": "Dispensar sugestão",
"header.actions.toggleTerminalPanelAria": "Mostrar ou ocultar painel de terminal",
@@ -1727,6 +1727,12 @@ export const settingsDict = {
"settings.openchamber.visual.field.sessionRecapAria": "Генерувати підсумок після завершення роботи агента",
"settings.openchamber.visual.field.sessionSuggestion": "Генерувати пропозицію наступного повідомлення користувача",
"settings.openchamber.visual.field.sessionSuggestionAria": "Генерувати запропоноване наступне повідомлення користувача після завершення роботи агента",
"settings.openchamber.visual.field.sessionGoal": "Увімкнути цілі сесії",
"settings.openchamber.visual.field.sessionGoalAria": "Автоматично продовжувати роботу сесії до досягнення цілі",
"settings.openchamber.visual.goal.sectionTitle": "Ціль",
"settings.openchamber.visual.goal.budgetLabel": "Типовий бюджет токенів",
"settings.openchamber.visual.goal.budgetAria": "Застосовувати типовий бюджет токенів до нових цілей",
"settings.openchamber.visual.goal.description": "Увімкніть кнопку-мішень у полі вводу — і наступне повідомлення стане ціллю: агент автоматично працюватиме над нею під наглядом малої моделі, навіть поки вас немає поруч.",
"settings.openchamber.visual.field.showReasoningTracesAria": "Показати сліди міркувань",
"settings.openchamber.visual.field.showReasoningTraces": "Показати сліди міркувань",
"settings.openchamber.visual.field.collapsibleThinkingBlocksAria": "Увімкнути згортальні блоки міркувань",
+37
View File
@@ -293,6 +293,10 @@ export const dict: Record<I18nKey, string> = {
"sessions.scheduledTasks.editor.prompt.placeholder": "Підсумуйте відкриті завдання й запропонуйте наступні дії",
"sessions.scheduledTasks.editor.enabled.aria": "Увімкнути завдання",
"sessions.scheduledTasks.editor.enabled.label": "Увімкнено",
"sessions.scheduledTasks.editor.goal.label": "Виконати як ціль",
"sessions.scheduledTasks.editor.goal.aria": "Виконати це завдання як ціль, яку агент веде до завершення",
"sessions.scheduledTasks.editor.goal.budgetLabel": "Бюджет токенів",
"sessions.scheduledTasks.editor.goal.budgetAria": "Обмежити ціль бюджетом токенів",
"sessions.scheduledTasks.editor.actions.closeAria": "Закрити",
"sessions.scheduledTasks.editor.actions.cancel": "Скасувати",
"sessions.scheduledTasks.editor.actions.save": "Зберегти",
@@ -1389,6 +1393,39 @@ export const dict: Record<I18nKey, string> = {
"header.actions.terminalPanelWithShortcut": "Термінальна панель ({shortcut})",
"chat.recap.aria": "Підсумок сесії",
"chat.recap.label": "Підсумок:",
"chat.goal.dialog.titleCreate": "Встановити ціль сесії",
"chat.goal.dialog.titleManage": "Ціль сесії",
"chat.goal.dialog.objectiveLabel": "Ціль",
"chat.goal.dialog.objectivePlaceholder": "Опишіть кінцевий стан, якого агент має досягти та перевірити…",
"chat.goal.dialog.budgetLabel": "Бюджет токенів",
"chat.goal.status.active": "Активна",
"chat.goal.status.evaluating": "Оцінювання…",
"chat.goal.status.paused": "Призупинена",
"chat.goal.status.blocked": "Заблокована",
"chat.goal.status.budgetLimited": "Бюджет вичерпано",
"chat.goal.status.complete": "Завершена",
"chat.goal.usage.tokens": "{used} токенів",
"chat.goal.usage.tokensWithBudget": "{used}/{budget} токенів",
"chat.goal.usage.turns": "продовжень: {turns}",
"chat.goal.action.pause": "Призупинити",
"chat.goal.action.resume": "Відновити",
"chat.goal.action.markComplete": "Позначити завершеною",
"chat.goal.action.clear": "Видалити ціль",
"chat.goal.action.cancel": "Скасувати",
"chat.goal.action.save": "Зберегти ціль",
"chat.goal.action.start": "Розпочати ціль",
"chat.goal.toast.actionFailed": "Не вдалося оновити ціль",
"chat.goal.row.aria": "Ціль сесії — відкрити деталі",
"chat.goal.button.createAria": "Встановити ціль сесії",
"chat.goal.button.manageAria": "Керувати ціллю сесії",
"chat.goal.button.armAria": "Розпочати ціль наступним повідомленням",
"chat.goal.counter.aria": "Ліміт довжини цілі",
"chat.goal.button.disarmAria": "Ціль увімкнена — торкніться, щоб вимкнути",
"chat.goal.button.cancelAria": "Ціль виконується — торкніться, щоб скасувати",
"chat.goal.cancelDialog.title": "Скасувати цю ціль?",
"chat.goal.cancelDialog.description": "Агент припинить автоматично працювати над цією ціллю.",
"chat.goal.cancelDialog.keep": "Залишити ціль",
"chat.goal.cancelDialog.confirm": "Скасувати ціль",
"chat.suggestion.applyAria": "Використати запропоноване повідомлення",
"chat.suggestion.dismissAria": "Прибрати пропозицію",
"header.actions.toggleTerminalPanelAria": "Перемкнути панель терміналу",
@@ -1727,6 +1727,12 @@ export const settingsDict = {
'settings.openchamber.visual.field.sessionRecapAria': '代理完成后生成回顾',
'settings.openchamber.visual.field.sessionSuggestion': '生成下一条用户消息建议',
'settings.openchamber.visual.field.sessionSuggestionAria': '代理完成后生成下一条用户消息建议',
'settings.openchamber.visual.field.sessionGoal': '启用会话目标',
'settings.openchamber.visual.field.sessionGoalAria': '让会话自动朝着目标持续工作',
'settings.openchamber.visual.goal.sectionTitle': '目标',
'settings.openchamber.visual.goal.budgetLabel': '默认令牌预算',
'settings.openchamber.visual.goal.budgetAria': '为新目标应用默认令牌预算',
'settings.openchamber.visual.goal.description': '在输入框中启用靶心按钮,下一条消息即成为目标:代理将在小模型的审核下自动持续工作,即使你不在电脑前。',
'settings.openchamber.visual.field.showReasoningTracesAria': '显示推理轨迹',
'settings.openchamber.visual.field.showReasoningTraces': '显示推理轨迹',
'settings.openchamber.visual.field.collapsibleThinkingBlocksAria': '启用可折叠推理块',
@@ -293,6 +293,10 @@ export const dict: Record<I18nKey, string> = {
'sessions.scheduledTasks.editor.prompt.placeholder': '总结未完成任务并给出下一步建议',
'sessions.scheduledTasks.editor.enabled.aria': '启用任务',
'sessions.scheduledTasks.editor.enabled.label': '已启用',
'sessions.scheduledTasks.editor.goal.label': '作为目标运行',
'sessions.scheduledTasks.editor.goal.aria': '将此任务作为代理持续推进直至完成的目标运行',
'sessions.scheduledTasks.editor.goal.budgetLabel': '令牌预算',
'sessions.scheduledTasks.editor.goal.budgetAria': '用令牌预算限制该目标',
'sessions.scheduledTasks.editor.actions.closeAria': '关闭',
'sessions.scheduledTasks.editor.actions.cancel': '取消',
'sessions.scheduledTasks.editor.actions.save': '保存',
@@ -1377,6 +1381,39 @@ export const dict: Record<I18nKey, string> = {
'header.actions.terminalPanelWithShortcut': '终端面板({shortcut}',
'chat.recap.aria': '会话回顾',
'chat.recap.label': '回顾:',
'chat.goal.dialog.titleCreate': '设置会话目标',
'chat.goal.dialog.titleManage': '会话目标',
'chat.goal.dialog.objectiveLabel': '目标',
'chat.goal.dialog.objectivePlaceholder': '描述代理应达到并验证的最终状态…',
'chat.goal.dialog.budgetLabel': '令牌预算',
'chat.goal.status.active': '进行中',
'chat.goal.status.evaluating': '评估中…',
'chat.goal.status.paused': '已暂停',
'chat.goal.status.blocked': '已阻塞',
'chat.goal.status.budgetLimited': '已达预算',
'chat.goal.status.complete': '已完成',
'chat.goal.usage.tokens': '{used} 令牌',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} 令牌',
'chat.goal.usage.turns': '{turns} 次续跑',
'chat.goal.action.pause': '暂停',
'chat.goal.action.resume': '继续',
'chat.goal.action.markComplete': '标记为完成',
'chat.goal.action.clear': '移除目标',
'chat.goal.action.cancel': '取消',
'chat.goal.action.save': '保存目标',
'chat.goal.action.start': '启动目标',
'chat.goal.toast.actionFailed': '目标更新失败',
'chat.goal.row.aria': '会话目标 — 打开详情',
'chat.goal.button.createAria': '设置会话目标',
'chat.goal.button.manageAria': '管理会话目标',
'chat.goal.button.armAria': '用下一条消息启动目标',
'chat.goal.counter.aria': '目标长度限制',
'chat.goal.button.disarmAria': '目标已就绪 — 点按取消就绪',
'chat.goal.button.cancelAria': '目标进行中 — 点按取消',
'chat.goal.cancelDialog.title': '取消此目标?',
'chat.goal.cancelDialog.description': '代理将停止自动朝该目标工作。',
'chat.goal.cancelDialog.keep': '保留目标',
'chat.goal.cancelDialog.confirm': '取消目标',
'chat.suggestion.applyAria': '使用建议的消息',
'chat.suggestion.dismissAria': '关闭建议',
'header.actions.toggleTerminalPanelAria': '切换终端面板',
@@ -1638,6 +1638,12 @@
'settings.openchamber.visual.field.sessionRecapAria': '代理完成後產生回顧',
'settings.openchamber.visual.field.sessionSuggestion': '產生下一則使用者訊息建議',
'settings.openchamber.visual.field.sessionSuggestionAria': '代理完成後產生下一則使用者訊息建議',
'settings.openchamber.visual.field.sessionGoal': '啟用工作階段目標',
'settings.openchamber.visual.field.sessionGoalAria': '讓工作階段自動朝目標持續工作',
'settings.openchamber.visual.goal.sectionTitle': '目標',
'settings.openchamber.visual.goal.budgetLabel': '預設權杖預算',
'settings.openchamber.visual.goal.budgetAria': '為新目標套用預設權杖預算',
'settings.openchamber.visual.goal.description': '在輸入框中啟用靶心按鈕,下一則訊息即成為目標:代理將在小型模型的稽核下自動持續工作,即使你不在電腦前。',
'settings.openchamber.visual.field.showReasoningTracesAria': '顯示推理軌跡',
'settings.openchamber.visual.field.showReasoningTraces': '顯示推理軌跡',
'settings.openchamber.visual.field.collapsibleThinkingBlocksAria': '啟用可摺疊推理區塊',
@@ -306,6 +306,10 @@ export const dict: Record<I18nKey, string> = {
'sessions.scheduledTasks.editor.prompt.placeholder': '總結未完成任務並給出下一步建議',
'sessions.scheduledTasks.editor.enabled.aria': '啟用任務',
'sessions.scheduledTasks.editor.enabled.label': '已啟用',
'sessions.scheduledTasks.editor.goal.label': '作為目標執行',
'sessions.scheduledTasks.editor.goal.aria': '將此任務作為代理持續推進直至完成的目標執行',
'sessions.scheduledTasks.editor.goal.budgetLabel': '權杖預算',
'sessions.scheduledTasks.editor.goal.budgetAria': '以權杖預算限制該目標',
'sessions.scheduledTasks.editor.actions.closeAria': '關閉',
'sessions.scheduledTasks.editor.actions.cancel': '取消',
'sessions.scheduledTasks.editor.actions.save': '儲存',
@@ -1381,6 +1385,39 @@ export const dict: Record<I18nKey, string> = {
'header.actions.terminalPanelWithShortcut': '終端機面板({shortcut}',
'chat.recap.aria': '工作階段回顧',
'chat.recap.label': '回顧:',
'chat.goal.dialog.titleCreate': '設定工作階段目標',
'chat.goal.dialog.titleManage': '工作階段目標',
'chat.goal.dialog.objectiveLabel': '目標',
'chat.goal.dialog.objectivePlaceholder': '描述代理應達成並驗證的最終狀態…',
'chat.goal.dialog.budgetLabel': '權杖預算',
'chat.goal.status.active': '進行中',
'chat.goal.status.evaluating': '評估中…',
'chat.goal.status.paused': '已暫停',
'chat.goal.status.blocked': '已受阻',
'chat.goal.status.budgetLimited': '已達預算',
'chat.goal.status.complete': '已完成',
'chat.goal.usage.tokens': '{used} 權杖',
'chat.goal.usage.tokensWithBudget': '{used}/{budget} 權杖',
'chat.goal.usage.turns': '{turns} 次續跑',
'chat.goal.action.pause': '暫停',
'chat.goal.action.resume': '繼續',
'chat.goal.action.markComplete': '標記為完成',
'chat.goal.action.clear': '移除目標',
'chat.goal.action.cancel': '取消',
'chat.goal.action.save': '儲存目標',
'chat.goal.action.start': '啟動目標',
'chat.goal.toast.actionFailed': '目標更新失敗',
'chat.goal.row.aria': '工作階段目標 — 開啟詳細資訊',
'chat.goal.button.createAria': '設定工作階段目標',
'chat.goal.button.manageAria': '管理工作階段目標',
'chat.goal.button.armAria': '用下一則訊息啟動目標',
'chat.goal.counter.aria': '目標長度限制',
'chat.goal.button.disarmAria': '目標已就緒 — 點按取消就緒',
'chat.goal.button.cancelAria': '目標進行中 — 點按取消',
'chat.goal.cancelDialog.title': '取消此目標?',
'chat.goal.cancelDialog.description': '代理將停止自動朝此目標工作。',
'chat.goal.cancelDialog.keep': '保留目標',
'chat.goal.cancelDialog.confirm': '取消目標',
'chat.suggestion.applyAria': '使用建議的訊息',
'chat.suggestion.dismissAria': '關閉建議',
'header.actions.toggleTerminalPanelAria': '切換終端機面板',
@@ -15,6 +15,17 @@ export const EXECUTION_FORK_DEFAULT_INSTRUCTIONS =
"if it is a conclusion or summary, your task is to verify it, explain whether you agree or disagree, and correct it if needed. " +
"Always clearly state what you understand your task to be, and wait for the user's approval of your conclusions before taking any further actions.";
// Assertive variant prefilled when "Run as goal" is checked: the new session
// must treat the forked message as an assignment and execute it to completion
// (the goal loop audits progress and keeps it going), not report back and wait.
export const EXECUTION_FORK_GOAL_INSTRUCTIONS =
"The message I share is an assignment handed over from another AI agent. Extract the concrete task from it and start executing immediately: " +
"if it is an implementation plan, implement that plan; " +
"if it is a conclusion or summary, verify it against the actual current state of the code and correct it if needed; " +
"if it is a bug description, find the root cause and fix it. " +
"Do not stop at restating your understanding and do not wait for approval — keep working until the task is verifiably complete, " +
"and end every turn with a factual statement of what is done, what was verified, and what remains.";
// Fixed connective that opens the forked assistant content. Not editable by the
// user — it sits between the user's instructions and the assistant message.
const EXECUTION_FORK_CONTENT_PREFACE =
+18
View File
@@ -429,6 +429,15 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
if (typeof settings.sessionSuggestionEnabled === 'boolean' && settings.sessionSuggestionEnabled !== store.sessionSuggestionEnabled) {
store.setSessionSuggestionEnabled(settings.sessionSuggestionEnabled);
}
if (typeof settings.sessionGoalEnabled === 'boolean' && settings.sessionGoalEnabled !== store.sessionGoalEnabled) {
store.setSessionGoalEnabled(settings.sessionGoalEnabled);
}
if (typeof settings.sessionGoalDefaultBudgetEnabled === 'boolean' && settings.sessionGoalDefaultBudgetEnabled !== store.sessionGoalDefaultBudgetEnabled) {
store.setSessionGoalDefaultBudgetEnabled(settings.sessionGoalDefaultBudgetEnabled);
}
if (typeof settings.sessionGoalDefaultBudget === 'number' && Number.isFinite(settings.sessionGoalDefaultBudget) && settings.sessionGoalDefaultBudget !== store.sessionGoalDefaultBudget) {
store.setSessionGoalDefaultBudget(settings.sessionGoalDefaultBudget);
}
if (typeof settings.collapsibleThinkingBlocks === 'boolean' && settings.collapsibleThinkingBlocks !== store.collapsibleThinkingBlocks) {
store.setCollapsibleThinkingBlocks(settings.collapsibleThinkingBlocks);
}
@@ -789,6 +798,15 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
if (typeof candidate.sessionSuggestionEnabled === 'boolean') {
result.sessionSuggestionEnabled = candidate.sessionSuggestionEnabled;
}
if (typeof candidate.sessionGoalEnabled === 'boolean') {
result.sessionGoalEnabled = candidate.sessionGoalEnabled;
}
if (typeof candidate.sessionGoalDefaultBudgetEnabled === 'boolean') {
result.sessionGoalDefaultBudgetEnabled = candidate.sessionGoalDefaultBudgetEnabled;
}
if (typeof candidate.sessionGoalDefaultBudget === 'number' && Number.isFinite(candidate.sessionGoalDefaultBudget) && candidate.sessionGoalDefaultBudget > 0) {
result.sessionGoalDefaultBudget = Math.floor(candidate.sessionGoalDefaultBudget);
}
if (typeof candidate.collapsibleThinkingBlocks === 'boolean') {
result.collapsibleThinkingBlocks = candidate.collapsibleThinkingBlocks;
}
+2
View File
@@ -21,6 +21,8 @@ export type ScheduledTask = {
modelID: string;
variant?: string;
agent?: string;
goalEnabled?: boolean;
goalTokenBudget?: number;
};
state: {
createdAt: number;
+124
View File
@@ -0,0 +1,124 @@
import { abortCurrentOperation, patchSessionMetadata } from '@/sync/session-actions';
import {
SESSION_GOAL_OBJECTIVE_CHAR_LIMIT,
type SessionGoalPayload,
type SessionGoalStatus,
} from '@/lib/sessionGoalMetadata';
const isRecord = (value: unknown): value is Record<string, unknown> =>
Boolean(value) && typeof value === 'object' && !Array.isArray(value);
const createGoalId = (): string =>
`${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
const writeGoal = (
sessionId: string,
directory: string | undefined,
update: (currentGoal: Record<string, unknown> | null) => Record<string, unknown> | null,
) =>
patchSessionMetadata(sessionId, directory, (metadata) => {
const namespace = isRecord(metadata.openchamber) ? metadata.openchamber : {};
const currentGoal = isRecord(namespace.goal) ? namespace.goal : null;
const nextGoal = update(currentGoal);
const nextNamespace = { ...namespace };
if (nextGoal) {
nextNamespace.goal = nextGoal;
} else {
delete nextNamespace.goal;
}
return { ...metadata, openchamber: nextNamespace };
});
export interface SetSessionGoalInput {
objective: string;
tokenBudget: number | null;
}
/**
* Create a new goal (fresh id resets accounting) or edit the existing one
* (id and usage counters preserved).
*/
export async function setSessionGoal(
sessionId: string,
directory: string | undefined,
input: SetSessionGoalInput,
existing: SessionGoalPayload | null,
): Promise<void> {
const objective = input.objective.trim().slice(0, SESSION_GOAL_OBJECTIVE_CHAR_LIMIT);
if (!objective) {
throw new Error('Goal objective must not be empty');
}
const tokenBudget = typeof input.tokenBudget === 'number' && Number.isFinite(input.tokenBudget) && input.tokenBudget > 0
? Math.floor(input.tokenBudget)
: null;
const now = Date.now();
await writeGoal(sessionId, directory, (currentGoal) => {
if (existing && currentGoal && currentGoal.id === existing.id && existing.status !== 'complete') {
// Edit in place: keep accounting, reactivate, clear stale audit state.
return {
...currentGoal,
objective,
tokenBudget,
status: 'active',
statusReason: 'resumed',
blockedStreak: 0,
updatedAt: now,
};
}
return {
id: createGoalId(),
objective,
status: 'active',
tokenBudget,
tokensUsed: 0,
turnsUsed: 0,
blockedStreak: 0,
note: '',
statusReason: '',
lastAccountedMessageID: '',
createdAt: now,
updatedAt: now,
};
});
}
export async function setSessionGoalStatus(
sessionId: string,
directory: string | undefined,
status: Extract<SessionGoalStatus, 'active' | 'paused' | 'complete'>,
): Promise<void> {
// Pausing a goal also stops the agent's current turn — same mental model
// as the stop button, expressed through goal control. A no-op when the
// session is already idle.
if (status === 'paused') {
void abortCurrentOperation(sessionId);
}
await writeGoal(sessionId, directory, (currentGoal) => {
if (!currentGoal) return null;
return {
...currentGoal,
status,
// 'resumed' is the server's kickoff signal for an already-idle session.
statusReason: status === 'active' ? 'resumed' : (status === 'complete' ? 'marked by user' : ''),
blockedStreak: 0,
// An explicit resume grants a fresh auto-continuation allowance —
// otherwise a goal blocked on the turn cap would re-block on the very
// next tick and Resume would be a dead end.
...(status === 'active' ? { turnsUsed: 0 } : {}),
updatedAt: Date.now(),
};
});
}
export async function clearSessionGoal(sessionId: string, directory: string | undefined): Promise<void> {
let wasActive = false;
await writeGoal(sessionId, directory, (currentGoal) => {
wasActive = currentGoal?.status === 'active';
return null;
});
// Removing a running goal is a "stop" too — abort the current turn like
// pause does. A no-op when the session is idle.
if (wasActive) {
void abortCurrentOperation(sessionId);
}
}
@@ -0,0 +1,74 @@
import type { Session } from '@opencode-ai/sdk/v2';
// Session goal driven by the server's session-goal runtime, stored under
// session.metadata.openchamber.goal. The UI writes goals (create/edit/
// pause/resume/clear) by patching this metadata; the server loop accounts
// usage, audits progress with the small model, and auto-continues the
// session until the goal settles.
export type SessionGoalStatus = 'active' | 'paused' | 'blocked' | 'budgetLimited' | 'complete';
const SESSION_GOAL_STATUSES: SessionGoalStatus[] = ['active', 'paused', 'blocked', 'budgetLimited', 'complete'];
export const SESSION_GOAL_OBJECTIVE_CHAR_LIMIT = 2000;
export interface SessionGoalPayload {
id: string;
objective: string;
status: SessionGoalStatus;
tokenBudget: number | null;
tokensUsed: number;
turnsUsed: number;
blockedStreak: number;
note: string;
statusReason: string;
lastAccountedMessageID: string;
createdAt: number;
updatedAt: number;
}
const isRecord = (value: unknown): value is Record<string, unknown> =>
Boolean(value) && typeof value === 'object' && !Array.isArray(value);
const isGoalStatus = (value: unknown): value is SessionGoalStatus =>
typeof value === 'string' && (SESSION_GOAL_STATUSES as string[]).includes(value);
export function getSessionGoal(session: Session | null | undefined): SessionGoalPayload | null {
const metadata = (session as { metadata?: unknown } | null | undefined)?.metadata;
if (!isRecord(metadata)) return null;
const namespace = metadata.openchamber;
if (!isRecord(namespace)) return null;
const goal = namespace.goal;
if (!isRecord(goal)) return null;
const id = typeof goal.id === 'string' ? goal.id : '';
const objective = typeof goal.objective === 'string' ? goal.objective.trim() : '';
if (!id || !objective || !isGoalStatus(goal.status)) return null;
const tokenBudget = typeof goal.tokenBudget === 'number' && Number.isFinite(goal.tokenBudget) && goal.tokenBudget > 0
? Math.floor(goal.tokenBudget)
: null;
const asCount = (value: unknown): number =>
typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
return {
id,
objective: objective.slice(0, SESSION_GOAL_OBJECTIVE_CHAR_LIMIT),
status: goal.status,
tokenBudget,
tokensUsed: asCount(goal.tokensUsed),
turnsUsed: asCount(goal.turnsUsed),
blockedStreak: asCount(goal.blockedStreak),
note: typeof goal.note === 'string' ? goal.note : '',
statusReason: typeof goal.statusReason === 'string' ? goal.statusReason : '',
lastAccountedMessageID: typeof goal.lastAccountedMessageID === 'string' ? goal.lastAccountedMessageID : '',
createdAt: typeof goal.createdAt === 'number' ? goal.createdAt : 0,
updatedAt: typeof goal.updatedAt === 'number' ? goal.updatedAt : 0,
};
}
export function formatGoalTokens(count: number): string {
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
if (count >= 10_000) return `${Math.round(count / 1000)}K`;
if (count >= 1_000) return `${(count / 1000).toFixed(1)}K`;
return String(count);
}
@@ -0,0 +1,20 @@
import type { SessionGoalStatus } from '@/lib/sessionGoalMetadata';
// Shared presentation mapping for the goal status across chat, sidebar and
// mobile surfaces. Colors are theme tokens; labels resolve through i18n at
// the call site.
export const sessionGoalStatusColor: Record<SessionGoalStatus, string> = {
active: 'var(--status-info)',
paused: 'var(--surface-muted-foreground)',
blocked: 'var(--status-warning)',
budgetLimited: 'var(--status-warning)',
complete: 'var(--status-success)',
};
export const sessionGoalStatusLabelKey: Record<SessionGoalStatus, string> = {
active: 'chat.goal.status.active',
paused: 'chat.goal.status.paused',
blocked: 'chat.goal.status.blocked',
budgetLimited: 'chat.goal.status.budgetLimited',
complete: 'chat.goal.status.complete',
};
+14
View File
@@ -186,6 +186,20 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
titleKey: 'settings.openchamber.visual.field.sessionSuggestion',
keywords: ['suggestion', 'assist', 'small model', 'follow up'],
},
{
id: 'chat.session-goal',
page: 'chat',
titleKey: 'settings.openchamber.visual.field.sessionGoal',
keywords: ['goal', 'objective', 'auto continue', 'small model'],
isAvailable: (ctx) => !ctx.isVSCode,
},
{
id: 'chat.session-goal-budget',
page: 'chat',
titleKey: 'settings.openchamber.visual.goal.budgetLabel',
keywords: ['goal', 'budget', 'tokens', 'limit'],
isAvailable: (ctx) => !ctx.isVSCode,
},
{
id: 'chat.reasoning-traces',
page: 'chat',