diff --git a/CHANGELOG.md b/CHANGELOG.md index a17485683d..77b954397c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ ### Added - CI now runs a headless browser smoke test (`tests/browser_smoke.py`, `.github/workflows/browser-smoke.yml`) on every PR and push: it boots the real server agent-free and loads the key pages in Chromium, failing on any console error or uncaught JS exception. This catches the runtime-JS brick class (e.g. a `const` reassigned at runtime as in #3162, or a `function`/`window` name collision as in #2715/#2771) that `node --check`, ESLint, and the mocked test suite cannot see because they only manifest when a real browser executes the page. The smoke is credential-free — it strips `*_API_KEY` from the environment and drives no real model. +### Changed + +- Activity disclosures now have a Settings → Appearance option to expand the activity feed by default, while still honoring per-turn manual collapse/expand choices. +- Live Activity waiting rows now explain the agent's current step instead of stopping at `Waiting on model`, including a prompt/context review hint before tools run and the latest tool-derived action while the model chooses the next step. + ## [v0.51.181] — 2026-05-30 — Release FA (stage-batchA — agent-cache eviction teardown + streaming finalize race) ### Fixed diff --git a/api/config.py b/api/config.py index 8fa873b63f..dd5cc13cd8 100644 --- a/api/config.py +++ b/api/config.py @@ -4822,6 +4822,7 @@ def _get_session_agent_lock(session_id: str) -> threading.Lock: "font_size": "default", # small | default | large | xlarge "session_jump_buttons": False, # show Start/End transcript jump pills "session_endless_scroll": False, # auto-load older transcript pages while scrolling upward + "activity_feed_expanded_default": False, # expand Activity disclosures by default for new turns "pinned_sessions_limit": 3, # maximum active pinned sessions shown in the sidebar "inflight_state_max_sessions": 8, # max active-stream recovery snapshots kept in browser localStorage "inflight_state_max_messages": 24, # max recent messages kept per recovery snapshot @@ -4985,6 +4986,7 @@ def load_settings() -> dict: "api_redact_enabled", "session_jump_buttons", "session_endless_scroll", + "activity_feed_expanded_default", } # Language codes are validated as short alphanumeric BCP-47-like tags (e.g. 'en', 'zh', 'fr') _SETTINGS_LANG_RE = __import__("re").compile(r"^[a-zA-Z]{2,10}(-[a-zA-Z0-9]{2,8})?$") diff --git a/static/boot.js b/static/boot.js index 9136a02856..15da1b79fc 100644 --- a/static/boot.js +++ b/static/boot.js @@ -1562,6 +1562,7 @@ function applyBotName(){ window._whatsNewSummaryEnabled=!!s.whats_new_summary_enabled; window._showThinking=s.show_thinking!==false; window._simplifiedToolCalling=s.simplified_tool_calling!==false; + window._activityFeedExpandedDefault=!!s.activity_feed_expanded_default; window._sidebarDensity=(s.sidebar_density==='detailed'?'detailed':'compact'); window._pinnedSessionsLimit=parseInt(s.pinned_sessions_limit||3,10)||3; window._inflightStateLimits={ diff --git a/static/i18n.js b/static/i18n.js index 82a68a81ac..e6406c747d 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -531,6 +531,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Load older messages while scrolling up', settings_desc_session_endless_scroll: 'When enabled, older messages load automatically as you scroll upward. When disabled, use the older-messages button.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Sidebar tabs', settings_desc_tab_visibility: 'Choose which tabs appear in the sidebar and rail. Chat and Settings are always visible.', @@ -1842,6 +1844,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Carica messaggi precedenti scorrendo in alto', settings_desc_session_endless_scroll: 'Se abilitato, i messaggi precedenti si caricano automaticamente scorrendo in alto. Se disabilitato, usa il pulsante messaggi precedenti.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Schede della barra laterale', settings_desc_tab_visibility: 'Scegli quali schede mostrare nella barra laterale e nel rail. Chat e Impostazioni sono sempre visibili.', @@ -3145,6 +3149,8 @@ const LOCALES = { settings_label_session_endless_scroll: '上スクロールで古いメッセージを読み込む', settings_desc_session_endless_scroll: '有効にすると、上にスクロールしたとき古いメッセージを自動で読み込みます。無効の場合は古いメッセージボタンを使います。', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'サイドバータブ', settings_desc_tab_visibility: 'サイドバーとレールに表示するタブを選択します。チャットと設定は常に表示されます。', @@ -5018,6 +5024,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Загружать старые сообщения при прокрутке вверх', settings_desc_session_endless_scroll: 'Если включено, старые сообщения загружаются автоматически при прокрутке вверх. Если выключено, используйте кнопку загрузки старых сообщений.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Вкладки боковой панели', settings_desc_tab_visibility: 'Выберите, какие вкладки отображаются на боковой панели и в рейле. Чат и настройки всегда видны.', @@ -6247,6 +6255,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Cargar mensajes antiguos al desplazarse hacia arriba', settings_desc_session_endless_scroll: 'Si está activado, los mensajes antiguos se cargan automáticamente al desplazarte hacia arriba. Si está desactivado, usa el botón de mensajes antiguos.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Pestañas de la barra lateral', settings_desc_tab_visibility: 'Elige qué pestañas aparecen en la barra lateral y el rail. Chat y Configuración siempre están visibles.', @@ -7188,6 +7198,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Ältere Nachrichten beim Hochscrollen laden', settings_desc_session_endless_scroll: 'Wenn aktiviert, werden ältere Nachrichten beim Hochscrollen automatisch geladen. Wenn deaktiviert, nutzt du den Button für ältere Nachrichten.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Seitenleiste-Tabs', settings_desc_tab_visibility: 'Wähle, welche Tabs in der Seitenleiste und im Rail angezeigt werden. Chat und Einstellungen sind immer sichtbar.', @@ -8737,6 +8749,8 @@ const LOCALES = { settings_label_session_endless_scroll: '向上滚动时加载更早的消息', settings_desc_session_endless_scroll: '启用后,向上滚动时会自动加载更早的消息。禁用时请使用加载更早消息按钮。', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: '侧边栏标签', settings_desc_tab_visibility: '选择在侧边栏和导航栏中显示哪些标签。聊天和设置始终可见。', @@ -9231,6 +9245,8 @@ const LOCALES = { settings_label_session_endless_scroll: '向上捲動時載入較早訊息', settings_desc_session_endless_scroll: '啟用後,向上捲動時會自動載入較早訊息。停用時請使用載入較早訊息按鈕。', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: '側邊欄標籤', settings_desc_tab_visibility: '選擇在側邊欄和導航列中顯示哪些標籤。聊天和設定始終可見。', @@ -10610,6 +10626,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Carregar mensagens antigas ao rolar para cima', settings_desc_session_endless_scroll: 'Quando ativado, mensagens antigas carregam automaticamente ao rolar para cima. Quando desativado, use o botão de mensagens antigas.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Abas da barra lateral', settings_desc_tab_visibility: 'Escolha quais abas aparecem na barra lateral e no rail. Chat e Configurações estão sempre visíveis.', @@ -11816,6 +11834,8 @@ const LOCALES = { settings_label_session_endless_scroll: '위로 스크롤할 때 이전 메시지 불러오기', settings_desc_session_endless_scroll: '활성화하면 위로 스크롤할 때 이전 메시지를 자동으로 불러옵니다. 비활성화하면 이전 메시지 버튼을 사용합니다.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: '사이드바 탭', settings_desc_tab_visibility: '사이드바와 레일에 표시할 탭을 선택하세요. 채팅과 설정은 항상 표시됩니다.', @@ -13035,6 +13055,8 @@ const LOCALES = { settings_desc_session_jump_buttons: 'Affichez les boutons flottants de début et de fin lors de la lecture de longs historiques de session.', settings_label_session_endless_scroll: 'Charger les anciens messages en faisant défiler vers le haut', settings_desc_session_endless_scroll: 'Lorsqu\'ils sont activés, les anciens messages se chargent automatiquement lorsque vous faites défiler vers le haut. Lorsqu\'il est désactivé, utilisez le bouton des messages plus anciens.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Onglets de la barre latérale', settings_desc_tab_visibility: 'Choisissez quels onglets apparaissent dans la barre latérale et le rail. Chat et Paramètres sont toujours visibles.', @@ -14357,6 +14379,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Yukarı kaydırırken eski mesajları yükle', settings_desc_session_endless_scroll: 'Etkinleştirildiğinde, yukarı doğru kaydırdığınızda eski mesajlar otomatik olarak yüklenir. Devre dışı bırakıldığında eski mesajlar düğmesini kullanın.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Kenar çubuğu sekmeleri', settings_desc_tab_visibility: 'Kenar çubuğunda ve rayda hangi sekmelerin görüneceğini seçin. Sohbet ve Ayarlar her zaman görünür durumdadır.', diff --git a/static/index.html b/static/index.html index 5969bde249..7df69902ed 100644 --- a/static/index.html +++ b/static/index.html @@ -981,6 +981,13 @@

What can I help with?

When enabled, older messages load automatically as you scroll upward. When disabled, use the older-messages button.
+
+ +
Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.
+
diff --git a/static/panels.js b/static/panels.js index fc8d4757fd..ef91a70825 100644 --- a/static/panels.js +++ b/static/panels.js @@ -5801,6 +5801,7 @@ function _appearancePayloadFromUi(){ font_size: ($('settingsFontSize')||{}).value || localStorage.getItem('hermes-font-size') || 'default', session_jump_buttons: !!($('settingsSessionJumpButtons')||{}).checked, session_endless_scroll: !!($('settingsSessionEndlessScroll')||{}).checked, + activity_feed_expanded_default: !!($('settingsActivityFeedExpandedDefault')||{}).checked, hidden_tabs: _getHiddenTabs(), }; } @@ -5854,6 +5855,9 @@ async function _autosaveAppearanceSettings(payload){ if(typeof _applySessionNavigationPrefs==='function') _applySessionNavigationPrefs(); } window._sessionEndlessScrollEnabled=!!(saved&&saved.session_endless_scroll); + if(saved&&Object.prototype.hasOwnProperty.call(saved,'activity_feed_expanded_default')){ + window._activityFeedExpandedDefault=!!saved.activity_feed_expanded_default; + } _setAppearanceAutosaveStatus('saved'); }catch(e){ console.warn('[settings] appearance autosave failed', e); @@ -6068,6 +6072,15 @@ async function loadSettingsPanel(){ _scheduleAppearanceAutosave(); }; } + const activityExpandedCb=$('settingsActivityFeedExpandedDefault'); + if(activityExpandedCb){ + activityExpandedCb.checked=!!settings.activity_feed_expanded_default; + window._activityFeedExpandedDefault=activityExpandedCb.checked; + activityExpandedCb.onchange=function(){ + window._activityFeedExpandedDefault=this.checked; + _scheduleAppearanceAutosave(); + }; + } // Tab visibility chips (dynamically populated from DOM) var hiddenTabs=[]; if(Array.isArray(settings.hidden_tabs)){ diff --git a/static/ui.js b/static/ui.js index 4ca46c0f9b..9118fd49b4 100644 --- a/static/ui.js +++ b/static/ui.js @@ -5523,10 +5523,13 @@ function ensureActivityGroup(inner, opts){ if(!group){ group=document.createElement('div'); let collapsed=opts.collapsed!==false; + if(window._activityFeedExpandedDefault===true) collapsed=false; const savedState=_readActivityDisclosureState(activityKey); // Restore the user's explicit expand intent when recreating the live // activity group within the same turn (#1298), then let persisted chat/turn - // state win across session switches and reloads. + // state win across session switches and reloads. Saved closed-state should + // override the default-expanded preference for settled groups the user has + // explicitly collapsed. if(live && _liveActivityUserExpanded === true) collapsed=false; else if(live && _liveActivityUserExpanded === false) collapsed=true; if(savedState==='open') collapsed=false; @@ -7087,14 +7090,33 @@ function _activityProgressLabelForToolName(name){ return 'Working'; } +function _activityLatestToolName(group){ + if(!group) return ''; + const running=group.querySelector('.tool-card.tool-card-running .tool-card-name'); + const latest=running || Array.from(group.querySelectorAll('.tool-card-name')).pop(); + return latest?String(latest.textContent||'').trim():''; +} + +function _activityWaitingDetail(group,label=''){ + const toolName=_activityLatestToolName(group); + if(toolName){ + const action=_activityProgressLabelForToolName(toolName); + if(group&&group.querySelector('.tool-card.tool-card-running')) return `${action}: ${toolName}. Results will appear here.`; + return `Last step: ${action} (${toolName}); now choosing the next action or composing a response.`; + } + if(String(label||'').toLowerCase().includes('model')) return 'Reviewing the prompt and context, then choosing the next action or composing the response.'; + return 'The agent is running; tool results and response text will appear here.'; +} + function _activityLiveProgressLabel(group){ if(!group||group.getAttribute('data-live-tool-call-group')!=='1') return ''; const idleAge=_activityLastObservedAge(group); if(idleAge!==null&&idleAge>=90) return `No recent activity for ${_formatActiveElapsedTimer(idleAge)}`; const running=group.querySelector('.tool-card.tool-card-running .tool-card-name'); - const latest=running || Array.from(group.querySelectorAll('.tool-card-name')).pop(); + const latest=running?String(running.textContent||'').trim():_activityLatestToolName(group); const waiting=group.querySelector('.agent-activity-status-waiting .agent-activity-status-label'); - if(latest) return _activityProgressLabelForToolName(latest.textContent); + if(latest) return _activityProgressLabelForToolName(latest); + if(waiting&&waiting.textContent&&String(waiting.textContent).toLowerCase().includes('model')) return 'Reviewing prompt and context'; if(waiting&&waiting.textContent) return waiting.textContent; return 'Starting agent'; } @@ -7173,8 +7195,13 @@ function appendLiveToolCard(tc){ status:toolDone?(tc.is_error?'error':'done'):'waiting', ts:_activityNowSeconds(), }); - const waiting=body.querySelector('.agent-activity-status[data-activity-event-id="thinking-placeholder"] .agent-activity-status-label'); - if(waiting&&!toolDone) waiting.textContent='Waiting on tool result'; + const waiting=body.querySelector('.agent-activity-status[data-activity-event-id="thinking-placeholder"]'); + if(waiting&&!toolDone){ + const labelEl=waiting.querySelector('.agent-activity-status-label'); + const detailEl=waiting.querySelector('.agent-activity-status-detail'); + if(labelEl) labelEl.textContent='Waiting on tool result'; + if(detailEl) detailEl.textContent=`${_activityProgressLabelForToolName(toolName)}: ${toolName}. Results will appear here.`; + } // Update existing card in place (tool_complete after tool_start) if(tid){ const existing=body.querySelector(`.tool-card-row[data-live-tid="${CSS.escape(tid)}"]`); @@ -7969,7 +7996,7 @@ function finalizeThinkingCard(){ // when the user has not manually expanded this turn's activity group, or // has manually collapsed it. Otherwise the panel snaps shut whenever new // activity arrives, even mid-read. - if(_liveActivityUserExpanded !== true){ + if(_liveActivityUserExpanded !== true && !(window._activityFeedExpandedDefault === true && _liveActivityUserExpanded !== false)){ group.classList.add('tool-call-group-collapsed'); const summary=group.querySelector('.tool-call-group-summary'); if(summary) summary.setAttribute('aria-expanded','false'); @@ -8047,10 +8074,10 @@ function appendThinking(text='', options){ detail='Creating the stream and sending your message…'; }else if(hasRunningTool){ label='Waiting on tool result'; - detail='The tool is still running; the response will continue after it completes.'; + detail=_activityWaitingDetail(group,label); }else if(hasToolCard){ label='Waiting on model'; - detail='Tool finished; waiting for the model to continue.'; + detail=_activityWaitingDetail(group,label); }else{ label='Waiting for first model token'; detail='Stream connected; no model output has arrived yet.'; diff --git a/tests/test_live_activity_timeline.py b/tests/test_live_activity_timeline.py index d1205ffa7f..26d337c813 100644 --- a/tests/test_live_activity_timeline.py +++ b/tests/test_live_activity_timeline.py @@ -29,9 +29,10 @@ def test_empty_thinking_placeholder_becomes_status_row_not_raw_thinking_card(): assert "Waiting for first model token" in UI_JS assert "Stream connected; no model output has arrived yet." in UI_JS assert "Waiting on model" in UI_JS - assert "Tool finished; waiting for the model to continue." in UI_JS + assert "Reviewing the prompt and context, then choosing the next action or composing the response." in UI_JS + assert "Reviewing prompt and context" in UI_JS assert "Waiting on tool result" in UI_JS - assert "The tool is still running; the response will continue after it completes." in UI_JS + assert "Last step: ${action} (${toolName}); now choosing the next action or composing a response." in UI_JS assert "_thinkingActivityNode(thinkingText, false)" in UI_JS @@ -45,6 +46,23 @@ def test_stream_start_refreshes_waiting_status_after_stream_id_arrives(): assert refresh_idx < attach_idx +def test_activity_feed_default_expand_setting_is_wired(): + index_html = (REPO / "static" / "index.html").read_text(encoding="utf-8") + panels_js = (REPO / "static" / "panels.js").read_text(encoding="utf-8") + boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8") + config_py = (REPO / "api" / "config.py").read_text(encoding="utf-8") + + assert 'id="settingsActivityFeedExpandedDefault"' in index_html + assert "settings_label_activity_feed_expanded_default" in index_html + assert '"activity_feed_expanded_default": False' in config_py + assert "activity_feed_expanded_default" in panels_js + assert "window._activityFeedExpandedDefault=!!s.activity_feed_expanded_default;" in boot_js + assert "if(window._activityFeedExpandedDefault===true) collapsed=false;" in UI_JS + finalize_fn = UI_JS.split("function finalizeThinkingCard")[1].split("\nfunction ")[0] + assert "_activityFeedExpandedDefault" in finalize_fn + assert "_liveActivityUserExpanded !== false" in finalize_fn + + def test_tool_events_update_activity_timeline_and_summary(): assert "Tool finished: ${toolName}" in UI_JS assert "Running tool: ${toolName}" in UI_JS