diff --git a/api/compression_anchor.py b/api/compression_anchor.py index 3a457d57..f251851c 100644 --- a/api/compression_anchor.py +++ b/api/compression_anchor.py @@ -53,6 +53,24 @@ def _content_has_part_type(content, part_types): ) +def _is_context_compression_marker(message): + """Return true for synthetic compression/reference cards, not user turns.""" + if not isinstance(message, dict): + return False + role = message.get("role") + if not role or role == "tool": + return False + text = _content_text( + message.get("content", ""), + part_types={"text", "input_text", "output_text"}, + ).lower().lstrip() + return ( + text.startswith("[context compaction") + or text.startswith("context compaction") + or text.startswith("[your active task list was preserved across context compression]") + ) + + def visible_messages_for_anchor(messages, *, auto_compression: bool = False): """Return transcript messages that can anchor compression UI metadata. @@ -70,6 +88,8 @@ def visible_messages_for_anchor(messages, *, auto_compression: bool = False): role = message.get("role") if not role or role == "tool": continue + if _is_context_compression_marker(message): + continue content = message.get("content", "") has_attachments = bool(message.get("attachments")) diff --git a/api/models.py b/api/models.py index 6ff74869..1a22ec9d 100644 --- a/api/models.py +++ b/api/models.py @@ -377,6 +377,11 @@ class Session: compression_anchor_message_key=None, compression_anchor_summary=None, pre_compression_snapshot: bool=False, + context_engine=None, + compression_anchor_engine=None, + compression_anchor_mode=None, + compression_anchor_details=None, + context_engine_state=None, context_length=None, threshold_tokens=None, last_prompt_tokens=None, gateway_routing=None, gateway_routing_history=None, @@ -417,6 +422,11 @@ class Session: self.compression_anchor_message_key = compression_anchor_message_key self.compression_anchor_summary = compression_anchor_summary self.pre_compression_snapshot = bool(pre_compression_snapshot) + self.context_engine = context_engine + self.compression_anchor_engine = compression_anchor_engine + self.compression_anchor_mode = compression_anchor_mode + self.compression_anchor_details = compression_anchor_details if isinstance(compression_anchor_details, dict) else {} + self.context_engine_state = context_engine_state if isinstance(context_engine_state, dict) else {} self.context_length = context_length self.threshold_tokens = threshold_tokens self.last_prompt_tokens = last_prompt_tokens @@ -474,6 +484,8 @@ class Session: 'pending_user_message', 'pending_attachments', 'pending_started_at', 'compression_anchor_visible_idx', 'compression_anchor_message_key', 'compression_anchor_summary', 'pre_compression_snapshot', + 'context_engine', 'compression_anchor_engine', 'compression_anchor_mode', + 'compression_anchor_details', 'context_engine_state', 'context_length', 'threshold_tokens', 'last_prompt_tokens', 'gateway_routing', 'gateway_routing_history', 'llm_title_generated', 'parent_session_id', @@ -641,6 +653,11 @@ class Session: 'compression_anchor_message_key': self.compression_anchor_message_key, 'compression_anchor_summary': self.compression_anchor_summary, 'pre_compression_snapshot': self.pre_compression_snapshot, + 'context_engine': self.context_engine, + 'compression_anchor_engine': self.compression_anchor_engine, + 'compression_anchor_mode': self.compression_anchor_mode, + 'compression_anchor_details': self.compression_anchor_details, + 'context_engine_state': self.context_engine_state, 'context_length': self.context_length, 'threshold_tokens': self.threshold_tokens, 'last_prompt_tokens': self.last_prompt_tokens, diff --git a/static/i18n.js b/static/i18n.js index c9e92360..4982119c 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -196,6 +196,8 @@ const LOCALES = { conversation_cleared: 'Conversation cleared', command_label: 'Command', context_compaction_label: 'Context compaction', + retrieval_context_label: 'Indexed context', + retrieval_context_preview: 'Earlier messages are stored and retrievable with context tools', preserved_task_list_label: 'Preserved task list', reference_only_label: 'Reference only', model_usage: 'Usage: /model ', @@ -1417,6 +1419,8 @@ const LOCALES = { conversation_cleared: 'Conversazione cancellata', command_label: 'Comando', context_compaction_label: 'Compattazione contesto', + retrieval_context_label: 'Contesto indicizzato', + retrieval_context_preview: 'I messaggi precedenti sono archiviati e recuperabili con gli strumenti di contesto', preserved_task_list_label: 'Lista task preservata', reference_only_label: 'Solo riferimento', model_usage: 'Uso: /model ', @@ -2630,6 +2634,8 @@ const LOCALES = { conversation_cleared: '会話をクリアしました', command_label: 'コマンド', context_compaction_label: 'コンテキスト圧縮', + retrieval_context_label: 'インデックス済みコンテキスト', + retrieval_context_preview: '以前のメッセージは保存され、コンテキストツールで取得できます', preserved_task_list_label: '保持されたタスクリスト', reference_only_label: '参照専用', model_usage: '使い方: /model <名前>', @@ -3883,6 +3889,8 @@ const LOCALES = { compress_failed_label: 'Ошибка сжатия', compress_running_label: 'Сжатие…', context_compaction_label: 'Сжатие контекста', + retrieval_context_label: 'Индексированный контекст', + retrieval_context_preview: 'Предыдущие сообщения сохранены и доступны через инструменты контекста', preserved_task_list_label: 'Сохранённый список задач', focus_label: 'Фокус', model_search_no_results: 'Модели не найдены', @@ -4992,6 +5000,8 @@ const LOCALES = { conversation_cleared: 'Conversación borrada', command_label: 'Comando', context_compaction_label: 'Compacción de contexto', + retrieval_context_label: 'Contexto indexado', + retrieval_context_preview: 'Los mensajes anteriores se almacenan y se pueden recuperar con herramientas de contexto', preserved_task_list_label: 'Lista de tareas conservada', reference_only_label: 'Solo referencia', model_usage: 'Uso: /model ', @@ -6118,6 +6128,8 @@ const LOCALES = { conversation_cleared: 'Konversation gelöscht', command_label: 'Befehl', context_compaction_label: 'Kontextkomprimierung', + retrieval_context_label: 'Indizierter Kontext', + retrieval_context_preview: 'Frühere Nachrichten sind gespeichert und über Kontextwerkzeuge abrufbar', preserved_task_list_label: 'Beibehaltene Aufgabenliste', reference_only_label: 'Nur Referenz', model_usage: 'Nutzung: /model ', @@ -7295,6 +7307,8 @@ const LOCALES = { conversation_cleared: '对话已清空', command_label: '命令', context_compaction_label: '上下文压缩', + retrieval_context_label: '已索引上下文', + retrieval_context_preview: '较早消息已存储,可通过上下文工具检索', preserved_task_list_label: '保留的任务列表', reference_only_label: '仅供参考', model_usage: '用法:/model ', @@ -10715,6 +10729,8 @@ const LOCALES = { conversation_cleared: '대화를 지웠습니다', command_label: '명령', context_compaction_label: 'Context compaction', + retrieval_context_label: 'Indexed context', + retrieval_context_preview: 'Earlier messages are stored and retrievable with context tools', preserved_task_list_label: '보존된 작업 목록', reference_only_label: 'Reference only', model_usage: 'Usage: /model ', diff --git a/static/messages.js b/static/messages.js index 6842bbb9..0cb687ee 100644 --- a/static/messages.js +++ b/static/messages.js @@ -1846,6 +1846,9 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ phase:'done', automatic:true, message, + engine:d.engine, + mode:d.mode, + details:d.details, summary:{headline:message}, continuationSessionId:continuationSid, }; diff --git a/static/ui.js b/static/ui.js index 8da479ab..b5bc8684 100644 --- a/static/ui.js +++ b/static/ui.js @@ -5096,9 +5096,10 @@ function _autoCompressionBaseDetail(state){ : (String(state&&state.message||fallback).trim()||fallback); } function _autoCompressionPreviewText(state){ + const copy=_engineAwareCompressionCopy(String(state&&state.engine||_compressionEngineForSession()).toLowerCase(), String(state&&state.mode||_compressionModeForSession()).toLowerCase()); const running=state&&state.phase==='running'; const detail=_autoCompressionBaseDetail(state); - if(!running) return (String(state&&state.summary?.headline||detail).trim()||detail); + if(!running) return (String(state&&state.summary?.headline||copy.preview||detail).trim()||detail); const elapsedLabel=_compressionElapsedLabel(state); return [detail, elapsedLabel].filter(Boolean).join(' · '); } @@ -5112,13 +5113,14 @@ function _autoCompressionDetailText(state){ return [base,handoff].filter(Boolean).join('\n'); } function _autoCompressionCardsHtml(state){ + const copy=_engineAwareCompressionCopy(String(state&&state.engine||_compressionEngineForSession()).toLowerCase(), String(state&&state.mode||_compressionModeForSession()).toLowerCase()); const running=state&&state.phase==='running'; const preview=_autoCompressionPreviewText(state); const cardDetail=_autoCompressionDetailText(state); return `
${_compressionStatusCardHtml({ - statusLabel: t('auto_compress_label'), + statusLabel: (String(state&&state.engine||'').toLowerCase()==='lcm'||String(state&&state.mode||'').toLowerCase()==='lossless_retrieval')?copy.label:t('auto_compress_label'), previewText: preview, detail: cardDetail, icon: running ? '' : li('check',13), @@ -5286,14 +5288,15 @@ function _latestCompressionReferenceMessage(messages, summaryText=''){ return {message:null, rawIdx:-1}; } function _compressionReferenceCardHtml(text, open=false){ + const copy=_engineAwareCompressionCopy(); const preview=text.split(/\n+/).filter(Boolean).slice(0,2).join(' '); return `
${li('star',13)} - ${esc(t('context_compaction_label'))} - ${esc(t('reference_only_label'))} · ${esc(preview)} + ${esc(copy.label)} + ${esc(copy.preview)} · ${esc(preview)} ${li('chevron-right',12)}
@@ -5367,6 +5370,31 @@ function _formatMessageFooterTimestamp(tsVal){ const opts={month:'short', day:'numeric', hour:'numeric', minute:'2-digit'}; return fmt?fmt(date,opts):date.toLocaleString([], opts); } +function _compressionEngineForSession(){ + return String( + (S.session&&( + S.session.compression_anchor_engine + || S.session.context_engine + )) || 'compressor' + ).trim().toLowerCase() || 'compressor'; +} +function _compressionModeForSession(){ + return String( + (S.session&&S.session.compression_anchor_mode) || 'summary_compaction' + ).trim().toLowerCase() || 'summary_compaction'; +} +function _engineAwareCompressionCopy(engine=_compressionEngineForSession(), mode=_compressionModeForSession()){ + if(engine==='lcm'||mode==='lossless_retrieval'){ + return { + label:t('retrieval_context_label'), + preview:t('retrieval_context_preview'), + }; + } + return { + label:t('context_compaction_label'), + preview:t('reference_only_label'), + }; +} function _compressionStatusCardHtml({ statusLabel, previewText, @@ -5946,6 +5974,7 @@ function renderMessages(options){ } function _insertCompressionLikeNodeByRawIdx(node, rawIdx){ if(!node) return; + if(rawIdx