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:
committed by
GitHub
parent
82c039117a
commit
bb45164ae8
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 l’objectif à 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 l’annuler',
|
||||
'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': '折りたたみ可能な推論ブロックを有効化',
|
||||
|
||||
@@ -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': '접을 수 있는 추론 블록 활성화',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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": "Увімкнути згортальні блоки міркувань",
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ export type ScheduledTask = {
|
||||
modelID: string;
|
||||
variant?: string;
|
||||
agent?: string;
|
||||
goalEnabled?: boolean;
|
||||
goalTokenBudget?: number;
|
||||
};
|
||||
state: {
|
||||
createdAt: number;
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user