#5213: Claude Code sidebar visibility toggle (#4714), rebased on master

This commit is contained in:
nesquena-hermes committed 2026-07-02 01:40:19 +00:00
1 parent f05679b09a
commit 4bbb64bcc1
8 files changed
+669 -22

No files matched your search

+2
View File
@@ -7999,6 +7999,7 @@ _SETTINGS_DEFAULTS = {
"show_tps": False, # show tokens-per-second chip in assistant message headers "show_tps": False, # show tokens-per-second chip in assistant message headers
"fade_text_effect": False, # animate newly streamed words with a lightweight fade-in effect "fade_text_effect": False, # animate newly streamed words with a lightweight fade-in effect
"show_cli_sessions": True, # merge CLI/TUI/messaging sessions from state.db into the sidebar by default (#3988); established installs are grandfathered OFF by the load_settings backfill "show_cli_sessions": True, # merge CLI/TUI/messaging sessions from state.db into the sidebar by default (#3988); established installs are grandfathered OFF by the load_settings backfill
"show_claude_code_sessions": True, # allow filtering Claude Code rows without hiding other imported sources
"show_cron_sessions": False, # surface cron sessions in the sidebar (subordinate to show_cli_sessions) "show_cron_sessions": False, # surface cron sessions in the sidebar (subordinate to show_cli_sessions)
"show_webhook_sessions": False, # surface webhook sessions in the sidebar (subordinate to show_cli_sessions) "show_webhook_sessions": False, # surface webhook sessions in the sidebar (subordinate to show_cli_sessions)
"show_previous_messaging_sessions": False, # show older Telegram/Discord/etc. reset segments "show_previous_messaging_sessions": False, # show older Telegram/Discord/etc. reset segments
@@ -8253,6 +8254,7 @@ _SETTINGS_BOOL_KEYS = {
"show_tps", "show_tps",
"fade_text_effect", "fade_text_effect",
"show_cli_sessions", "show_cli_sessions",
"show_claude_code_sessions",
"show_cron_sessions", "show_cron_sessions",
"show_webhook_sessions", "show_webhook_sessions",
"show_previous_messaging_sessions", "show_previous_messaging_sessions",
+56 -9
View File
@@ -3,6 +3,7 @@ import collections
import copy import copy
import datetime import datetime
import hashlib import hashlib
import inspect
import json import json
import logging import logging
import os import os
@@ -5227,6 +5228,19 @@ def _path_stat_cache_key(path):
return None return None
def _callable_accepts_include_claude_code(callable_obj) -> bool:
try:
signature = inspect.signature(callable_obj)
except (TypeError, ValueError):
return True
if 'include_claude_code' in signature.parameters:
return True
return any(
parameter.kind == inspect.Parameter.VAR_KEYWORD
for parameter in signature.parameters.values()
)
def _sqlite_content_fingerprint(db_path: Path): def _sqlite_content_fingerprint(db_path: Path):
"""Return a commit-reliable content fingerprint for a state.db. """Return a commit-reliable content fingerprint for a state.db.
@@ -5352,7 +5366,7 @@ def _cli_sessions_streaming_freeze_marker():
return ("streaming",) return ("streaming",)
def _resolve_cli_sessions_context(source_filter=None): def _resolve_cli_sessions_context(source_filter=None, include_claude_code: bool = True):
# Use the active WebUI profile's HERMES_HOME to find state.db. # Use the active WebUI profile's HERMES_HOME to find state.db.
# The active profile is determined by what the user has selected in the UI # The active profile is determined by what the user has selected in the UI
# (stored in the server's runtime config). This means: # (stored in the server's runtime config). This means:
@@ -5389,6 +5403,7 @@ def _resolve_cli_sessions_context(source_filter=None):
str(db_path), str(db_path),
str(source_filter or ''), str(source_filter or ''),
db_state_key, db_state_key,
bool(include_claude_code),
_path_cache_key(projects_dir), _path_cache_key(projects_dir),
_path_stat_cache_key(projects_dir), _path_stat_cache_key(projects_dir),
_path_stat_cache_key(SESSION_INDEX_FILE), _path_stat_cache_key(SESSION_INDEX_FILE),
@@ -5814,7 +5829,12 @@ def _load_cli_sessions_uncached(
return cli_sessions return cli_sessions
def get_cli_sessions(source_filter=None, *, all_profiles: bool = False) -> list: def get_cli_sessions(
source_filter=None,
*,
all_profiles: bool = False,
include_claude_code: bool = True,
) -> list:
"""Read CLI sessions from the agent's SQLite store and return them as """Read CLI sessions from the agent's SQLite store and return them as
dicts in a format the WebUI sidebar can render alongside local sessions. dicts in a format the WebUI sidebar can render alongside local sessions.
@@ -5834,34 +5854,61 @@ def get_cli_sessions(source_filter=None, *, all_profiles: bool = False) -> list:
cache_key = ( cache_key = (
'all_profiles', 'all_profiles',
source_filter or '', source_filter or '',
bool(include_claude_code),
context_cache_key, context_cache_key,
_path_cache_key(_default_claude_code_projects_dir()), _path_cache_key(_default_claude_code_projects_dir()),
_path_stat_cache_key(_default_claude_code_projects_dir()), _path_stat_cache_key(_default_claude_code_projects_dir()),
_path_stat_cache_key(SESSION_INDEX_FILE), _path_stat_cache_key(SESSION_INDEX_FILE),
) )
else: else:
hermes_home, db_path, cli_profile, cache_key = _resolve_cli_sessions_context(source_filter) resolve_kwargs = {}
resolve_supports_include_claude_code = _callable_accepts_include_claude_code(
_resolve_cli_sessions_context
)
if resolve_supports_include_claude_code:
resolve_kwargs['include_claude_code'] = include_claude_code
hermes_home, db_path, cli_profile, cache_key = _resolve_cli_sessions_context(
source_filter,
**resolve_kwargs,
)
if not resolve_supports_include_claude_code:
cache_key = cache_key + (bool(include_claude_code),)
ttl = _cli_sessions_cache_ttl_seconds() ttl = _cli_sessions_cache_ttl_seconds()
now = time.monotonic() now = time.monotonic()
def _load_sessions(): def _load_sessions():
loader_supports_include_claude_code = _callable_accepts_include_claude_code(
_load_cli_sessions_uncached
)
if all_profiles: if all_profiles:
merged: list[dict] = [] merged: list[dict] = []
for idx, (ctx_home, ctx_db_path, ctx_profile) in enumerate(contexts): for idx, (ctx_home, ctx_db_path, ctx_profile) in enumerate(contexts):
load_kwargs = {
'source_filter': source_filter,
'visible_session_limit': None,
'cron_project_limit': None,
'webhook_project_limit': None,
}
if loader_supports_include_claude_code:
load_kwargs['include_claude_code'] = include_claude_code and idx == 0
merged.extend( merged.extend(
_load_cli_sessions_uncached( _load_cli_sessions_uncached(
ctx_home, ctx_home,
ctx_db_path, ctx_db_path,
ctx_profile, ctx_profile,
source_filter=source_filter, **load_kwargs,
visible_session_limit=None,
cron_project_limit=None,
webhook_project_limit=None,
include_claude_code=(idx == 0),
) )
) )
return merged return merged
return _load_cli_sessions_uncached(hermes_home, db_path, cli_profile, source_filter=source_filter) load_kwargs = {'source_filter': source_filter}
if loader_supports_include_claude_code:
load_kwargs['include_claude_code'] = include_claude_code
return _load_cli_sessions_uncached(
hermes_home,
db_path,
cli_profile,
**load_kwargs,
)
if ttl > 0: if ttl > 0:
stale_sessions = None stale_sessions = None
+68 -10
View File
@@ -6,6 +6,7 @@ Extracted from server.py (Sprint 11) so server.py is a thin shell.
import html as _html import html as _html
import copy import copy
import hashlib import hashlib
import inspect
import errno import errno
import io import io
import gzip import gzip
@@ -1842,7 +1843,7 @@ _session_list_cache_claim_rebuild = _route_session_list_cache._session_list_cach
_session_list_cache_done = _route_session_list_cache._session_list_cache_done _session_list_cache_done = _route_session_list_cache._session_list_cache_done
_session_list_cache_get = _route_session_list_cache._session_list_cache_get _session_list_cache_get = _route_session_list_cache._session_list_cache_get
_session_list_cache_invalidation_stamp = _route_session_list_cache._session_list_cache_invalidation_stamp _session_list_cache_invalidation_stamp = _route_session_list_cache._session_list_cache_invalidation_stamp
_session_list_cache_key = _route_session_list_cache._session_list_cache_key _route_session_list_cache_key = _route_session_list_cache._session_list_cache_key
_session_list_cache_overlay_runtime_rows = _route_session_list_cache._session_list_cache_overlay_runtime_rows _session_list_cache_overlay_runtime_rows = _route_session_list_cache._session_list_cache_overlay_runtime_rows
_session_list_cache_path_stamp = _route_session_list_cache._session_list_cache_path_stamp _session_list_cache_path_stamp = _route_session_list_cache._session_list_cache_path_stamp
_session_list_cache_profile_scope = _route_session_list_cache._session_list_cache_profile_scope _session_list_cache_profile_scope = _route_session_list_cache._session_list_cache_profile_scope
@@ -1856,6 +1857,52 @@ _session_list_cache_state_db_fingerprint = _route_session_list_cache._session_li
_session_list_cache_stale_reason = _route_session_list_cache._session_list_cache_stale_reason _session_list_cache_stale_reason = _route_session_list_cache._session_list_cache_stale_reason
_session_list_cache_streaming_freeze_marker = _route_session_list_cache._session_list_cache_streaming_freeze_marker _session_list_cache_streaming_freeze_marker = _route_session_list_cache._session_list_cache_streaming_freeze_marker
def _callable_accepts_kwarg(callable_obj, kwarg_name: str) -> bool:
try:
signature = inspect.signature(callable_obj)
except (TypeError, ValueError):
return True
if kwarg_name in signature.parameters:
return True
return any(
parameter.kind == inspect.Parameter.VAR_KEYWORD
for parameter in signature.parameters.values()
)
def _session_list_cache_key(
active_profile: str | None,
all_profiles: bool,
show_cli_sessions: bool,
show_previous_messaging_sessions: bool,
show_cron_sessions: bool,
include_archived: bool = False,
exclude_hidden: bool = False,
visible_only: bool = False,
show_webhook_sessions: bool = False,
source_filter: str | None = None,
sidebar_source: str | None = None,
archived_limit: int | None = None,
archived_offset: int = 0,
show_claude_code_sessions: bool = True,
) -> tuple:
return _route_session_list_cache_key(
active_profile=active_profile,
all_profiles=all_profiles,
show_cli_sessions=show_cli_sessions,
show_previous_messaging_sessions=show_previous_messaging_sessions,
show_cron_sessions=show_cron_sessions,
include_archived=include_archived,
exclude_hidden=exclude_hidden,
visible_only=visible_only,
show_webhook_sessions=show_webhook_sessions,
source_filter=source_filter,
sidebar_source=sidebar_source,
archived_limit=archived_limit,
archived_offset=archived_offset,
) + (bool(show_claude_code_sessions),)
_ROUTE_SESSION_LIST_CACHE_DYNAMIC_EXPORTS = { _ROUTE_SESSION_LIST_CACHE_DYNAMIC_EXPORTS = {
"_SESSIONS_CACHE_ALL_PROFILES_INVALIDATION_VERSION", "_SESSIONS_CACHE_ALL_PROFILES_INVALIDATION_VERSION",
"_SESSIONS_CACHE_GLOBAL_INVALIDATION_VERSION", "_SESSIONS_CACHE_GLOBAL_INVALIDATION_VERSION",
@@ -2090,6 +2137,7 @@ def _build_session_list_cache_payload(
show_cli_sessions: bool, show_cli_sessions: bool,
show_previous_messaging_sessions: bool, show_previous_messaging_sessions: bool,
show_cron_sessions: bool, show_cron_sessions: bool,
show_claude_code_sessions: bool = True,
include_archived: bool = False, include_archived: bool = False,
exclude_hidden: bool = False, exclude_hidden: bool = False,
visible_only: bool = False, visible_only: bool = False,
@@ -2128,15 +2176,8 @@ def _build_session_list_cache_payload(
) )
def _all_sessions_for_sidebar(): def _all_sessions_for_sidebar():
try: if _callable_accepts_kwarg(all_sessions, "include_lineage_metadata"):
return all_sessions(diag=diag, include_lineage_metadata=False) return all_sessions(diag=diag, include_lineage_metadata=False)
except TypeError as exc:
message = str(exc)
if (
"unexpected keyword argument" not in message
or "include_lineage_metadata" not in message
):
raise
# Focused tests and third-party callers sometimes monkeypatch # Focused tests and third-party callers sometimes monkeypatch
# routes.all_sessions with the historical diag-only signature. # routes.all_sessions with the historical diag-only signature.
return all_sessions(diag=diag) return all_sessions(diag=diag)
@@ -2155,7 +2196,19 @@ def _build_session_list_cache_payload(
webui_sessions = [_normalize_sidebar_source_flags(s) for s in webui_sessions] webui_sessions = [_normalize_sidebar_source_flags(s) for s in webui_sessions]
if show_cli_sessions: if show_cli_sessions:
diag_stage("get_cli_sessions") diag_stage("get_cli_sessions")
cli = get_cli_sessions(source_filter=source_filter, all_profiles=all_profiles) if _callable_accepts_kwarg(get_cli_sessions, "include_claude_code"):
cli = get_cli_sessions(
source_filter=source_filter,
all_profiles=all_profiles,
include_claude_code=show_claude_code_sessions,
)
else:
# Focused tests sometimes monkeypatch routes.get_cli_sessions with
# the historical two-keyword signature.
cli = get_cli_sessions(
source_filter=source_filter,
all_profiles=all_profiles,
)
diag_stage("merge_cli_sessions") diag_stage("merge_cli_sessions")
cli_by_id = {s["session_id"]: s for s in cli} cli_by_id = {s["session_id"]: s for s in cli}
# #3238/#4591: reconcile orphaned imported sidecars. When a CLI or # #3238/#4591: reconcile orphaned imported sidecars. When a CLI or
@@ -2457,6 +2510,7 @@ def _build_session_list_cache_payload(
"show_cli_sessions": show_cli_sessions, "show_cli_sessions": show_cli_sessions,
"show_previous_messaging_sessions": show_previous_messaging_sessions, "show_previous_messaging_sessions": show_previous_messaging_sessions,
"show_cron_sessions": show_cron_sessions, "show_cron_sessions": show_cron_sessions,
"show_claude_code_sessions": show_claude_code_sessions if show_cli_sessions else False,
"show_webhook_sessions": show_webhook_sessions, "show_webhook_sessions": show_webhook_sessions,
}, },
} }
@@ -11964,6 +12018,7 @@ def handle_get(handler, parsed) -> bool:
diag.stage("load_settings") diag.stage("load_settings")
settings = load_settings() settings = load_settings()
show_cli_sessions = bool(settings.get("show_cli_sessions")) show_cli_sessions = bool(settings.get("show_cli_sessions"))
show_claude_code_sessions = bool(settings.get("show_claude_code_sessions"))
show_previous_messaging_sessions = bool( show_previous_messaging_sessions = bool(
settings.get("show_previous_messaging_sessions") settings.get("show_previous_messaging_sessions")
) )
@@ -11985,6 +12040,7 @@ def handle_get(handler, parsed) -> bool:
active_profile=active_profile, active_profile=active_profile,
all_profiles=all_profiles, all_profiles=all_profiles,
show_cli_sessions=show_cli_sessions, show_cli_sessions=show_cli_sessions,
show_claude_code_sessions=show_claude_code_sessions,
show_previous_messaging_sessions=show_previous_messaging_sessions, show_previous_messaging_sessions=show_previous_messaging_sessions,
show_cron_sessions=show_cron_sessions, show_cron_sessions=show_cron_sessions,
include_archived=include_archived, include_archived=include_archived,
@@ -12006,6 +12062,7 @@ def handle_get(handler, parsed) -> bool:
active_profile=active_profile, active_profile=active_profile,
all_profiles=all_profiles, all_profiles=all_profiles,
show_cli_sessions=show_cli_sessions, show_cli_sessions=show_cli_sessions,
show_claude_code_sessions=show_claude_code_sessions,
show_previous_messaging_sessions=show_previous_messaging_sessions, show_previous_messaging_sessions=show_previous_messaging_sessions,
show_cron_sessions=show_cron_sessions, show_cron_sessions=show_cron_sessions,
include_archived=include_archived, include_archived=include_archived,
@@ -14253,6 +14310,7 @@ def handle_post(handler, parsed) -> bool:
k in body k in body
for k in ( for k in (
"show_cli_sessions", "show_cli_sessions",
"show_claude_code_sessions",
"show_cron_sessions", "show_cron_sessions",
"show_webhook_sessions", "show_webhook_sessions",
"show_previous_messaging_sessions", "show_previous_messaging_sessions",
+28
View File
@@ -833,6 +833,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Sidebar density', settings_label_sidebar_density: 'Sidebar density',
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status', cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Show non-WebUI sessions', settings_label_external_sessions: 'Show non-WebUI sessions',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Show previous messaging sessions', settings_label_previous_messaging_sessions: 'Show previous messaging sessions',
@@ -1159,6 +1160,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Every 20 exchanges', settings_auto_title_refresh_20: 'Every 20 exchanges',
settings_desc_auto_title_refresh: 'Automatically re-generates the session title based on the latest exchange, keeping it relevant as the conversation evolves. Requires an LLM title generation model to be configured.', settings_desc_auto_title_refresh: 'Automatically re-generates the session title based on the latest exchange, keeping it relevant as the conversation evolves. Requires an LLM title generation model to be configured.',
settings_desc_external_sessions: 'Show conversations from CLI, Telegram, Discord, Slack, and other channels in the session list. Click to import and continue.', settings_desc_external_sessions: 'Show conversations from CLI, Telegram, Discord, Slack, and other channels in the session list. Click to import and continue.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Show older Discord, Telegram, Slack, and Weixin sessions that were replaced by reset or compression.', settings_desc_previous_messaging_sessions: 'Show older Discord, Telegram, Slack, and Weixin sessions that were replaced by reset or compression.',
@@ -2496,6 +2498,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Densità sidebar', settings_label_sidebar_density: 'Densità sidebar',
cmd_reasoning: 'Mostra/nascondi ragionamento, imposta livello sforzo o controlla stato attuale', cmd_reasoning: 'Mostra/nascondi ragionamento, imposta livello sforzo o controlla stato attuale',
settings_label_external_sessions: 'Mostra sessioni non-WebUI', settings_label_external_sessions: 'Mostra sessioni non-WebUI',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Mostra sessioni di messaggistica precedenti', settings_label_previous_messaging_sessions: 'Mostra sessioni di messaggistica precedenti',
@@ -2810,6 +2813,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Ogni 20 scambi', settings_auto_title_refresh_20: 'Ogni 20 scambi',
settings_desc_auto_title_refresh: 'Rigenera automaticamente il titolo della sessione in base all\'ultimo scambio, mantenendolo pertinente mentre la conversazione evolve. Richiede un modello LLM per la generazione titoli.', settings_desc_auto_title_refresh: 'Rigenera automaticamente il titolo della sessione in base all\'ultimo scambio, mantenendolo pertinente mentre la conversazione evolve. Richiede un modello LLM per la generazione titoli.',
settings_desc_external_sessions: 'Mostra conversazioni da CLI, Telegram, Discord, Slack e altri canali nella lista sessioni. Clicca per importare e continuare.', settings_desc_external_sessions: 'Mostra conversazioni da CLI, Telegram, Discord, Slack e altri canali nella lista sessioni. Clicca per importare e continuare.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Mostra sessioni Discord, Telegram, Slack e Weixin più vecchie sostituite da reset o compressione.', settings_desc_previous_messaging_sessions: 'Mostra sessioni Discord, Telegram, Slack e Weixin più vecchie sostituite da reset o compressione.',
@@ -4150,6 +4154,7 @@ const LOCALES = {
settings_label_sidebar_density: 'サイドバー密度', settings_label_sidebar_density: 'サイドバー密度',
cmd_reasoning: '思考表示の切り替え (表示/非表示)、努力レベル設定、現在状態の確認', cmd_reasoning: '思考表示の切り替え (表示/非表示)、努力レベル設定、現在状態の確認',
settings_label_external_sessions: '非WebUIセッションを表示', settings_label_external_sessions: '非WebUIセッションを表示',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Cronセッションを表示', settings_label_cron_sessions: 'Cronセッションを表示',
settings_label_webhook_sessions: 'Webhookセッションを表示', settings_label_webhook_sessions: 'Webhookセッションを表示',
settings_label_previous_messaging_sessions: '以前のメッセージングセッションを表示', settings_label_previous_messaging_sessions: '以前のメッセージングセッションを表示',
@@ -4469,6 +4474,7 @@ const LOCALES = {
settings_auto_title_refresh_20: '20 回ごと', settings_auto_title_refresh_20: '20 回ごと',
settings_desc_auto_title_refresh: '最新のやり取りに基づいてセッションタイトルを自動再生成し、会話の進行に合わせて適切に保ちます。LLM タイトル生成モデルの設定が必要です。', settings_desc_auto_title_refresh: '最新のやり取りに基づいてセッションタイトルを自動再生成し、会話の進行に合わせて適切に保ちます。LLM タイトル生成モデルの設定が必要です。',
settings_desc_external_sessions: 'CLI、Telegram、Discord、Slack その他のチャネルからの会話をセッション一覧に表示します。クリックでインポートして続行できます。', settings_desc_external_sessions: 'CLI、Telegram、Discord、Slack その他のチャネルからの会話をセッション一覧に表示します。クリックでインポートして続行できます。',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Cronジョブの出力をサイドバーの会話として表示します。WebUI以外のセッションが有効な場合のみ機能します。デフォルトはオフ。高頻度のジョブはサイドバーを溢れさせる可能性があります。', settings_desc_cron_sessions: 'Cronジョブの出力をサイドバーの会話として表示します。WebUI以外のセッションが有効な場合のみ機能します。デフォルトはオフ。高頻度のジョブはサイドバーを溢れさせる可能性があります。',
settings_desc_webhook_sessions: 'Webhook実行をサイドバーの会話として表示します。WebUI以外のセッションが有効な場合のみ機能します。デフォルトはオフ。高頻度のルートはサイドバーを溢れさせる可能性があります。', settings_desc_webhook_sessions: 'Webhook実行をサイドバーの会話として表示します。WebUI以外のセッションが有効な場合のみ機能します。デフォルトはオフ。高頻度のルートはサイドバーを溢れさせる可能性があります。',
settings_desc_previous_messaging_sessions: 'reset または compression によって置き換えられた以前の Discord、Telegram、Slack、Weixin セッションを表示します。', settings_desc_previous_messaging_sessions: 'reset または compression によって置き換えられた以前の Discord、Telegram、Slack、Weixin セッションを表示します。',
@@ -5514,6 +5520,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Плотность боковой панели', settings_label_sidebar_density: 'Плотность боковой панели',
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status', cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Показывать внешние сеансы', settings_label_external_sessions: 'Показывать внешние сеансы',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Показывать предыдущие сеансы обмена сообщениями', settings_label_previous_messaging_sessions: 'Показывать предыдущие сеансы обмена сообщениями',
@@ -5758,6 +5765,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Каждые 20 обменов', settings_auto_title_refresh_20: 'Каждые 20 обменов',
settings_desc_auto_title_refresh: 'Автоматически переформулирует заголовок сессии на основе последнего обмена, чтобы он оставался актуальным по мере развития беседы. Требует настроенную модель генерации заголовков.', settings_desc_auto_title_refresh: 'Автоматически переформулирует заголовок сессии на основе последнего обмена, чтобы он оставался актуальным по мере развития беседы. Требует настроенную модель генерации заголовков.',
settings_desc_external_sessions: 'Показать беседы из CLI, Telegram, Discord, Slack и других каналов в списке сеансов. Нажмите для импорта и продолжения.', settings_desc_external_sessions: 'Показать беседы из CLI, Telegram, Discord, Slack и других каналов в списке сеансов. Нажмите для импорта и продолжения.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Показывать предыдущие сеансы Discord, Telegram, Slack и Weixin, замененные сбросом или сжатием.', settings_desc_previous_messaging_sessions: 'Показывать предыдущие сеансы Discord, Telegram, Slack и Weixin, замененные сбросом или сжатием.',
@@ -7095,6 +7103,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Densidad de la barra lateral', settings_label_sidebar_density: 'Densidad de la barra lateral',
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status', cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Mostrar sesiones externas', settings_label_external_sessions: 'Mostrar sesiones externas',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Mostrar sesiones de mensajería anteriores', settings_label_previous_messaging_sessions: 'Mostrar sesiones de mensajería anteriores',
@@ -7350,6 +7359,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Cada 20 intercambios', settings_auto_title_refresh_20: 'Cada 20 intercambios',
settings_desc_auto_title_refresh: 'Regenera automáticamente el título de la sesión basándose en el último intercambio, manteniéndolo relevante a medida que evoluciona la conversación. Requiere un modelo LLM de generación de títulos configurado.', settings_desc_auto_title_refresh: 'Regenera automáticamente el título de la sesión basándose en el último intercambio, manteniéndolo relevante a medida que evoluciona la conversación. Requiere un modelo LLM de generación de títulos configurado.',
settings_desc_external_sessions: 'Mostrar conversaciones de CLI, Telegram, Discord, Slack y otros canales en la lista de sesiones. Haz clic para importar y continuar.', settings_desc_external_sessions: 'Mostrar conversaciones de CLI, Telegram, Discord, Slack y otros canales en la lista de sesiones. Haz clic para importar y continuar.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Mostrar sesiones antiguas de Discord, Telegram, Slack y Weixin reemplazadas por reset o compresión.', settings_desc_previous_messaging_sessions: 'Mostrar sesiones antiguas de Discord, Telegram, Slack y Weixin reemplazadas por reset o compresión.',
@@ -8664,6 +8674,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Seitenleistendichte', settings_label_sidebar_density: 'Seitenleistendichte',
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status', cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Externe Sitzungen anzeigen', settings_label_external_sessions: 'Externe Sitzungen anzeigen',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Vorherige Messaging-Sitzungen anzeigen', settings_label_previous_messaging_sessions: 'Vorherige Messaging-Sitzungen anzeigen',
@@ -8909,6 +8920,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Alle 20 Antworten', settings_auto_title_refresh_20: 'Alle 20 Antworten',
settings_desc_auto_title_refresh: 'Generiert den Sitzungstitel automatisch anhand des letzten Austauschs neu und hält ihn so aktuell, während sich das Gespräch entwickelt. Erfordert ein konfiguriertes LLM-Titelgenerierungsmodell.', settings_desc_auto_title_refresh: 'Generiert den Sitzungstitel automatisch anhand des letzten Austauschs neu und hält ihn so aktuell, während sich das Gespräch entwickelt. Erfordert ein konfiguriertes LLM-Titelgenerierungsmodell.',
settings_desc_external_sessions: 'Zeigt Gespräche von CLI, Telegram, Discord, Slack und anderen Kanälen in der Sitzungsliste an. Klicken zum Importieren und Fortsetzen.', settings_desc_external_sessions: 'Zeigt Gespräche von CLI, Telegram, Discord, Slack und anderen Kanälen in der Sitzungsliste an. Klicken zum Importieren und Fortsetzen.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Zeigt ältere Discord-, Telegram-, Slack- und Weixin-Sitzungen, die durch Reset oder Compression ersetzt wurden.', settings_desc_previous_messaging_sessions: 'Zeigt ältere Discord-, Telegram-, Slack- und Weixin-Sitzungen, die durch Reset oder Compression ersetzt wurden.',
@@ -10298,6 +10310,7 @@ const LOCALES = {
settings_label_sidebar_density: '侧边栏密度', settings_label_sidebar_density: '侧边栏密度',
cmd_reasoning: '切换思维可见性(显示/隐藏)、设置工作强度或查看当前状态', cmd_reasoning: '切换思维可见性(显示/隐藏)、设置工作强度或查看当前状态',
settings_label_external_sessions: '显示外部会话', settings_label_external_sessions: '显示外部会话',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: '显示定时任务会话', settings_label_cron_sessions: '显示定时任务会话',
settings_label_webhook_sessions: '显示Webhook会话', settings_label_webhook_sessions: '显示Webhook会话',
settings_label_previous_messaging_sessions: '显示以前的消息会话', settings_label_previous_messaging_sessions: '显示以前的消息会话',
@@ -10634,6 +10647,7 @@ const LOCALES = {
settings_auto_title_refresh_20: '每 20 轮对话', settings_auto_title_refresh_20: '每 20 轮对话',
settings_desc_auto_title_refresh: '基于最新对话自动重新生成会话标题,使其随对话发展保持相关。需要配置 LLM 标题生成模型。', settings_desc_auto_title_refresh: '基于最新对话自动重新生成会话标题,使其随对话发展保持相关。需要配置 LLM 标题生成模型。',
settings_desc_external_sessions: '在会话列表中显示来自 CLI、Telegram、Discord、Slack 等渠道的对话。点击可导入并继续对话。', settings_desc_external_sessions: '在会话列表中显示来自 CLI、Telegram、Discord、Slack 等渠道的对话。点击可导入并继续对话。',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: '将定时任务输出以对话形式显示在侧边栏。仅在启用非 WebUI 会话时此功能才会生效。默认关闭;高频任务可能会使侧边栏信息泛滥。', settings_desc_cron_sessions: '将定时任务输出以对话形式显示在侧边栏。仅在启用非 WebUI 会话时此功能才会生效。默认关闭;高频任务可能会使侧边栏信息泛滥。',
settings_desc_webhook_sessions: '将Webhook运行情况以对话形式显示在侧边栏。仅在启用非 WebUI 会话时此功能才会生效。默认关闭;大量的路由信息可能会使侧边栏信息泛滥。', settings_desc_webhook_sessions: '将Webhook运行情况以对话形式显示在侧边栏。仅在启用非 WebUI 会话时此功能才会生效。默认关闭;大量的路由信息可能会使侧边栏信息泛滥。',
settings_desc_previous_messaging_sessions: '显示被 reset 或 compression 替换的较旧的 Discord、Telegram、Slack 和 Weixin 会话。', settings_desc_previous_messaging_sessions: '显示被 reset 或 compression 替换的较旧的 Discord、Telegram、Slack 和 Weixin 会话。',
@@ -12171,6 +12185,7 @@ const LOCALES = {
settings_label_sidebar_density: '側邊欄密度', settings_label_sidebar_density: '側邊欄密度',
cmd_reasoning: '切換思考內容可見性(顯示/隱藏)、設定推理強度,或檢查目前狀態', cmd_reasoning: '切換思考內容可見性(顯示/隱藏)、設定推理強度,或檢查目前狀態',
settings_label_external_sessions: '顯示外部對話', settings_label_external_sessions: '顯示外部對話',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: '顯示以前的訊息對話', settings_label_previous_messaging_sessions: '顯示以前的訊息對話',
@@ -12493,6 +12508,7 @@ const LOCALES = {
settings_auto_title_refresh_20: '每 20 輪對話', settings_auto_title_refresh_20: '每 20 輪對話',
settings_desc_auto_title_refresh: '根據最新對話自動重新產生對話標題,讓標題隨對話發展保持相關。需要設定 LLM 標題產生模型。', settings_desc_auto_title_refresh: '根據最新對話自動重新產生對話標題,讓標題隨對話發展保持相關。需要設定 LLM 標題產生模型。',
settings_desc_external_sessions: '在對話清單中顯示來自 CLI、Telegram、Discord、Slack 等管道的對話。點選即可匯入並繼續對話。', settings_desc_external_sessions: '在對話清單中顯示來自 CLI、Telegram、Discord、Slack 等管道的對話。點選即可匯入並繼續對話。',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: '顯示被 reset 或 compression 替換的較舊的 Discord、Telegram、Slack 和 Weixin 對話。', settings_desc_previous_messaging_sessions: '顯示被 reset 或 compression 替換的較舊的 Discord、Telegram、Slack 和 Weixin 對話。',
@@ -13720,6 +13736,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Densidade da sidebar', settings_label_sidebar_density: 'Densidade da sidebar',
cmd_reasoning: 'Alternar visibilidade do pensamento (mostrar/ocultar)', cmd_reasoning: 'Alternar visibilidade do pensamento (mostrar/ocultar)',
settings_label_external_sessions: 'Mostrar sessões externas', settings_label_external_sessions: 'Mostrar sessões externas',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Mostrar sessões de mensagens anteriores', settings_label_previous_messaging_sessions: 'Mostrar sessões de mensagens anteriores',
@@ -13968,6 +13985,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'A cada 20 trocas', settings_auto_title_refresh_20: 'A cada 20 trocas',
settings_desc_auto_title_refresh: 'Re-gera título da sessão baseado na última troca.', settings_desc_auto_title_refresh: 'Re-gera título da sessão baseado na última troca.',
settings_desc_external_sessions: 'Mostrar conversas de CLI, Telegram, Discord, Slack e outros canais na lista de sessões. Clique para importar e continuar.', settings_desc_external_sessions: 'Mostrar conversas de CLI, Telegram, Discord, Slack e outros canais na lista de sessões. Clique para importar e continuar.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Mostrar sessões antigas de Discord, Telegram, Slack e Weixin substituídas por reset ou compressão.', settings_desc_previous_messaging_sessions: 'Mostrar sessões antigas de Discord, Telegram, Slack e Weixin substituídas por reset ou compressão.',
@@ -15273,6 +15291,7 @@ const LOCALES = {
settings_label_sidebar_density: '사이드바 밀도', settings_label_sidebar_density: '사이드바 밀도',
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status', cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: '외부 세션 표시', settings_label_external_sessions: '외부 세션 표시',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: '이전 메시징 세션 표시', settings_label_previous_messaging_sessions: '이전 메시징 세션 표시',
@@ -15520,6 +15539,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Every 20 exchanges', settings_auto_title_refresh_20: 'Every 20 exchanges',
settings_desc_auto_title_refresh: '최신 대화를 바탕으로 세션 제목을 자동으로 다시 생성해 대화가 진행되어도 제목을 관련 있게 유지합니다. LLM 제목 생성 모델 설정이 필요합니다.', settings_desc_auto_title_refresh: '최신 대화를 바탕으로 세션 제목을 자동으로 다시 생성해 대화가 진행되어도 제목을 관련 있게 유지합니다. LLM 제목 생성 모델 설정이 필요합니다.',
settings_desc_external_sessions: 'CLI, Telegram, Discord, Slack 및 기타 채널의 대화를 세션 목록에 표시합니다. 클릭하여 가져오고 계속하세요.', settings_desc_external_sessions: 'CLI, Telegram, Discord, Slack 및 기타 채널의 대화를 세션 목록에 표시합니다. 클릭하여 가져오고 계속하세요.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'reset 또는 compression으로 교체된 이전 Discord, Telegram, Slack, Weixin 세션을 표시합니다.', settings_desc_previous_messaging_sessions: 'reset 또는 compression으로 교체된 이전 Discord, Telegram, Slack, Weixin 세션을 표시합니다.',
@@ -16939,6 +16959,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Densité de la barre latérale', settings_label_sidebar_density: 'Densité de la barre latérale',
cmd_reasoning: 'Basculer la visibilité du raisonnement (afficher/masquer), définir le niveau d\'effort, ou vérifier l\'état actuel', cmd_reasoning: 'Basculer la visibilité du raisonnement (afficher/masquer), définir le niveau d\'effort, ou vérifier l\'état actuel',
settings_label_external_sessions: 'Afficher les sessions non-WebUI', settings_label_external_sessions: 'Afficher les sessions non-WebUI',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Afficher les sessions cron', settings_label_cron_sessions: 'Afficher les sessions cron',
settings_label_webhook_sessions: 'Afficher les sessions webhook', settings_label_webhook_sessions: 'Afficher les sessions webhook',
settings_label_previous_messaging_sessions: 'Afficher les sessions de messagerie précédentes', settings_label_previous_messaging_sessions: 'Afficher les sessions de messagerie précédentes',
@@ -17254,6 +17275,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Tous les 20 échanges', settings_auto_title_refresh_20: 'Tous les 20 échanges',
settings_desc_auto_title_refresh: 'Régénère automatiquement le titre de la session en fonction du dernier échange, le gardant pertinent à mesure que la conversation évolue. Nécessite qu\'un modèle de génération de titres LLM soit configuré.', settings_desc_auto_title_refresh: 'Régénère automatiquement le titre de la session en fonction du dernier échange, le gardant pertinent à mesure que la conversation évolue. Nécessite qu\'un modèle de génération de titres LLM soit configuré.',
settings_desc_external_sessions: 'Afficher les conversations de la CLI, Telegram, Discord, Slack et d\'autres canaux dans la liste des sessions. Cliquez pour importer et continuer.', settings_desc_external_sessions: 'Afficher les conversations de la CLI, Telegram, Discord, Slack et d\'autres canaux dans la liste des sessions. Cliquez pour importer et continuer.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Afficher les sorties des tâches cron comme des conversations dans la barre latérale. Uniquement actif lorsque les sessions non-WebUI sont activées. Désactivé par défaut ; les tâches à haute fréquence peuvent inonder la barre latérale.', settings_desc_cron_sessions: 'Afficher les sorties des tâches cron comme des conversations dans la barre latérale. Uniquement actif lorsque les sessions non-WebUI sont activées. Désactivé par défaut ; les tâches à haute fréquence peuvent inonder la barre latérale.',
settings_desc_webhook_sessions: 'Afficher les exécutions webhook comme des conversations dans la barre latérale. Uniquement actif lorsque les sessions non-WebUI sont activées. Désactivé par défaut ; les routes à volume élevé peuvent inonder la barre latérale.', settings_desc_webhook_sessions: 'Afficher les exécutions webhook comme des conversations dans la barre latérale. Uniquement actif lorsque les sessions non-WebUI sont activées. Désactivé par défaut ; les routes à volume élevé peuvent inonder la barre latérale.',
settings_desc_previous_messaging_sessions: 'Afficher les anciennes sessions Discord, Telegram, Slack et Weixin qui ont été remplacées par une réinitialisation ou une compression.', settings_desc_previous_messaging_sessions: 'Afficher les anciennes sessions Discord, Telegram, Slack et Weixin qui ont été remplacées par une réinitialisation ou une compression.',
@@ -18561,6 +18583,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Kenar çubuğu yoğunluğu', settings_label_sidebar_density: 'Kenar çubuğu yoğunluğu',
cmd_reasoning: 'Düşünme görünürlüğünü değiştirin (göster/gizle), çaba düzeyini ayarlayın veya mevcut durumu kontrol edin', cmd_reasoning: 'Düşünme görünürlüğünü değiştirin (göster/gizle), çaba düzeyini ayarlayın veya mevcut durumu kontrol edin',
settings_label_external_sessions: 'WebUI olmayan oturumları göster', settings_label_external_sessions: 'WebUI olmayan oturumları göster',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Show cron sessions', settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions', settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Önceki mesajlaşma oturumlarını göster', settings_label_previous_messaging_sessions: 'Önceki mesajlaşma oturumlarını göster',
@@ -18807,6 +18830,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Her 20 değişimde bir', settings_auto_title_refresh_20: 'Her 20 değişimde bir',
settings_desc_auto_title_refresh: 'Oturum başlıklarını en son konuşmaya göre otomatik olarak yeniden oluşturarak konuşma ilerledikçe başlıkların alakalı kalmasını sağlar. LLM başlık oluşturma modeli yapılandırması gerektirir.', settings_desc_auto_title_refresh: 'Oturum başlıklarını en son konuşmaya göre otomatik olarak yeniden oluşturarak konuşma ilerledikçe başlıkların alakalı kalmasını sağlar. LLM başlık oluşturma modeli yapılandırması gerektirir.',
settings_desc_external_sessions: 'Oturum listesinde CLI, Telegram, Discord, Slack ve diğer kanallardan gelen konuşmaları gösterin. İçe aktarmak ve devam etmek için tıklayın.', settings_desc_external_sessions: 'Oturum listesinde CLI, Telegram, Discord, Slack ve diğer kanallardan gelen konuşmaları gösterin. İçe aktarmak ve devam etmek için tıklayın.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.', settings_desc_cron_sessions: 'Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.',
settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.', settings_desc_webhook_sessions: 'Surface webhook runs as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-volume routes can flood the sidebar.',
settings_desc_previous_messaging_sessions: 'Sıfırlama veya sıkıştırmayla değiştirilen eski Discord, Telegram, Slack ve Weixin oturumlarını gösterin.', settings_desc_previous_messaging_sessions: 'Sıfırlama veya sıkıştırmayla değiştirilen eski Discord, Telegram, Slack ve Weixin oturumlarını gösterin.',
@@ -20229,6 +20253,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Gęstość paska bocznego', settings_label_sidebar_density: 'Gęstość paska bocznego',
cmd_reasoning: 'Przełącz widoczność myślenia (pokaż/ukryj), ustaw poziom wysiłku lub sprawdź bieżący status', cmd_reasoning: 'Przełącz widoczność myślenia (pokaż/ukryj), ustaw poziom wysiłku lub sprawdź bieżący status',
settings_label_external_sessions: 'Pokaż sesje inne niż WebUI', settings_label_external_sessions: 'Pokaż sesje inne niż WebUI',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Pokaż sesje cron', settings_label_cron_sessions: 'Pokaż sesje cron',
settings_label_webhook_sessions: 'Pokaż sesje webhooków', settings_label_webhook_sessions: 'Pokaż sesje webhooków',
settings_label_previous_messaging_sessions: 'Pokaż poprzednie sesje komunikatorów', settings_label_previous_messaging_sessions: 'Pokaż poprzednie sesje komunikatorów',
@@ -20545,6 +20570,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Co 20 wymian', settings_auto_title_refresh_20: 'Co 20 wymian',
settings_desc_auto_title_refresh: 'Automatycznie generuje na nowo tytuł konwersacji na podstawie najnowszej wymiany, utrzymując go adekwatnym w miarę rozwoju rozmowy. Wymaga skonfigurowanego modelu LLM do generowania tytułów.', settings_desc_auto_title_refresh: 'Automatycznie generuje na nowo tytuł konwersacji na podstawie najnowszej wymiany, utrzymując go adekwatnym w miarę rozwoju rozmowy. Wymaga skonfigurowanego modelu LLM do generowania tytułów.',
settings_desc_external_sessions: 'Pokaż konwersacje z CLI, Telegrama, Discorda, Slacka i innych kanałów na liście sesji. Kliknij, aby zaimportować i kontynuować.', settings_desc_external_sessions: 'Pokaż konwersacje z CLI, Telegrama, Discorda, Slacka i innych kanałów na liście sesji. Kliknij, aby zaimportować i kontynuować.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Wyświetlaj wyjście zadań cron jako konwersacje na pasku bocznym. Aktywne tylko wtedy, gdy włączone są sesje spoza WebUI. Domyślnie wyłączone; zadania o wysokiej częstotliwości mogą zalać pasek boczny.', settings_desc_cron_sessions: 'Wyświetlaj wyjście zadań cron jako konwersacje na pasku bocznym. Aktywne tylko wtedy, gdy włączone są sesje spoza WebUI. Domyślnie wyłączone; zadania o wysokiej częstotliwości mogą zalać pasek boczny.',
settings_desc_webhook_sessions: 'Wyświetlaj uruchomienia webhooków jako konwersacje na pasku bocznym. Aktywne tylko wtedy, gdy włączone są sesje spoza WebUI. Domyślnie wyłączone; trasy o dużym natężeniu mogą zalać pasek boczny.', settings_desc_webhook_sessions: 'Wyświetlaj uruchomienia webhooków jako konwersacje na pasku bocznym. Aktywne tylko wtedy, gdy włączone są sesje spoza WebUI. Domyślnie wyłączone; trasy o dużym natężeniu mogą zalać pasek boczny.',
settings_desc_previous_messaging_sessions: 'Pokaż starsze sesje z Discorda, Telegrama, Slacka i Weixin, które zostały zastąpione przez reset lub kompresję.', settings_desc_previous_messaging_sessions: 'Pokaż starsze sesje z Discorda, Telegrama, Slacka i Weixin, które zostały zastąpione przez reset lub kompresję.',
@@ -21725,6 +21751,7 @@ const LOCALES = {
settings_label_sidebar_density: 'Mật độ sidebar', settings_label_sidebar_density: 'Mật độ sidebar',
cmd_reasoning: 'Bật/tắt hiển thị suy nghĩ (show/hide), đặt mức effort hoặc kiểm tra trạng thái hiện tại', cmd_reasoning: 'Bật/tắt hiển thị suy nghĩ (show/hide), đặt mức effort hoặc kiểm tra trạng thái hiện tại',
settings_label_external_sessions: 'Hiện phiên không phải WebUI', settings_label_external_sessions: 'Hiện phiên không phải WebUI',
settings_label_claude_code_sessions: 'Show Claude Code sessions',
settings_label_cron_sessions: 'Hiện phiên cron', settings_label_cron_sessions: 'Hiện phiên cron',
settings_label_webhook_sessions: 'Hiện phiên webhook', settings_label_webhook_sessions: 'Hiện phiên webhook',
settings_label_previous_messaging_sessions: 'Hiện các phiên nhắn tin trước đó', settings_label_previous_messaging_sessions: 'Hiện các phiên nhắn tin trước đó',
@@ -22038,6 +22065,7 @@ const LOCALES = {
settings_auto_title_refresh_20: 'Mỗi 20 lượt trao đổi', settings_auto_title_refresh_20: 'Mỗi 20 lượt trao đổi',
settings_desc_auto_title_refresh: 'Tự động tạo lại tiêu đề phiên dựa trên lượt trao đổi mới nhất, giúp tiêu đề luôn phù hợp khi cuộc trò chuyện phát triển. Yêu cầu có model tạo tiêu đề LLM được cấu hình.', settings_desc_auto_title_refresh: 'Tự động tạo lại tiêu đề phiên dựa trên lượt trao đổi mới nhất, giúp tiêu đề luôn phù hợp khi cuộc trò chuyện phát triển. Yêu cầu có model tạo tiêu đề LLM được cấu hình.',
settings_desc_external_sessions: 'Hiển thị các cuộc trò chuyện từ CLI, Telegram, Discord, Slack và các kênh khác trong danh sách phiên. Bấm để import và tiếp tục.', settings_desc_external_sessions: 'Hiển thị các cuộc trò chuyện từ CLI, Telegram, Discord, Slack và các kênh khác trong danh sách phiên. Bấm để import và tiếp tục.',
settings_desc_claude_code_sessions: 'Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.',
settings_desc_cron_sessions: 'Hiển thị output của cron job như các cuộc trò chuyện trong sidebar. Chỉ hoạt động khi bật phiên không phải WebUI. Mặc định tắt; job tần suất cao có thể làm sidebar bị ngập.', settings_desc_cron_sessions: 'Hiển thị output của cron job như các cuộc trò chuyện trong sidebar. Chỉ hoạt động khi bật phiên không phải WebUI. Mặc định tắt; job tần suất cao có thể làm sidebar bị ngập.',
settings_desc_webhook_sessions: 'Hiển thị các lần chạy webhook như các cuộc trò chuyện trong sidebar. Chỉ hoạt động khi bật phiên không phải WebUI. Mặc định tắt; route lưu lượng cao có thể làm sidebar bị ngập.', settings_desc_webhook_sessions: 'Hiển thị các lần chạy webhook như các cuộc trò chuyện trong sidebar. Chỉ hoạt động khi bật phiên không phải WebUI. Mặc định tắt; route lưu lượng cao có thể làm sidebar bị ngập.',
settings_desc_previous_messaging_sessions: 'Hiển thị các phiên Discord, Telegram, Slack và Weixin cũ đã bị thay thế bởi reset hoặc nén.', settings_desc_previous_messaging_sessions: 'Hiển thị các phiên Discord, Telegram, Slack và Weixin cũ đã bị thay thế bởi reset hoặc nén.',
+7
View File
@@ -1344,6 +1344,13 @@
</label> </label>
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_cron_sessions">Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.</div> <div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_cron_sessions">Surface cron job output as conversations in the sidebar. Only active when non-WebUI sessions are enabled. Defaults off; high-frequency jobs can flood the sidebar.</div>
</div> </div>
<div class="settings-field" id="settingsShowClaudeCodeSessionsField" style="margin-left:24px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="settingsShowClaudeCodeSessions" style="width:15px;height:15px;accent-color:var(--accent)">
<span data-i18n="settings_label_claude_code_sessions">Show Claude Code sessions</span>
</label>
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_claude_code_sessions">Show Claude Code imported sessions in the sidebar, or hide them while leaving other external sessions visible.</div>
</div>
<div class="settings-field" id="settingsShowWebhookSessionsField" style="margin-left:24px"> <div class="settings-field" id="settingsShowWebhookSessionsField" style="margin-left:24px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer"> <label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="settingsShowWebhookSessions" style="width:15px;height:15px;accent-color:var(--accent)"> <input type="checkbox" id="settingsShowWebhookSessions" style="width:15px;height:15px;accent-color:var(--accent)">
+17 -1
View File
@@ -7915,6 +7915,8 @@ function _preferencesPayloadFromUi(){
if(apiRedactCb) payload.api_redact_enabled=apiRedactCb.checked; if(apiRedactCb) payload.api_redact_enabled=apiRedactCb.checked;
const showCliCb=$('settingsShowCliSessions'); const showCliCb=$('settingsShowCliSessions');
if(showCliCb) payload.show_cli_sessions=showCliCb.checked; if(showCliCb) payload.show_cli_sessions=showCliCb.checked;
const showClaudeCodeCb=$('settingsShowClaudeCodeSessions');
if(showClaudeCodeCb) payload.show_claude_code_sessions=showClaudeCodeCb.checked;
const showCronCb=$('settingsShowCronSessions'); const showCronCb=$('settingsShowCronSessions');
// Gate cron sessions on CLI sessions (the server short-circuits otherwise), // Gate cron sessions on CLI sessions (the server short-circuits otherwise),
// identically to the explicit saveSettings() path, so neither save route can // identically to the explicit saveSettings() path, so neither save route can
@@ -8447,12 +8449,23 @@ async function loadSettingsPanel(){
if(apiRedactCb){apiRedactCb.checked=settings.api_redact_enabled!==false;apiRedactCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});} if(apiRedactCb){apiRedactCb.checked=settings.api_redact_enabled!==false;apiRedactCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
const showCliCb=$('settingsShowCliSessions'); const showCliCb=$('settingsShowCliSessions');
if(showCliCb){showCliCb.checked=settings.show_cli_sessions!==false;showCliCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});} if(showCliCb){showCliCb.checked=settings.show_cli_sessions!==false;showCliCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
const showClaudeCodeCb=$('settingsShowClaudeCodeSessions');
if(showClaudeCodeCb){
showClaudeCodeCb.checked=!!settings.show_claude_code_sessions;
showClaudeCodeCb.disabled=showCliCb?!showCliCb.checked:true;
showClaudeCodeCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});
}
if(showCliCb){showCliCb.addEventListener('change',function(){
const enabled=!!showCliCb.checked;
if(showCronCb) showCronCb.disabled=!enabled;
if(showClaudeCodeCb) showClaudeCodeCb.disabled=!enabled;
_schedulePreferencesAutosave();
},{once:false});}
const showCronCb=$('settingsShowCronSessions'); const showCronCb=$('settingsShowCronSessions');
if(showCronCb){ if(showCronCb){
showCronCb.checked=!!settings.show_cron_sessions; showCronCb.checked=!!settings.show_cron_sessions;
showCronCb.disabled=showCliCb?!showCliCb.checked:true; showCronCb.disabled=showCliCb?!showCliCb.checked:true;
showCronCb.addEventListener('change',_schedulePreferencesAutosave,{once:false}); showCronCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});
if(showCliCb){showCliCb.addEventListener('change',function(){showCronCb.disabled=!showCliCb.checked;},{once:false});}
} }
const showWebhookCb=$('settingsShowWebhookSessions'); const showWebhookCb=$('settingsShowWebhookSessions');
if(showWebhookCb){ if(showWebhookCb){
@@ -11257,6 +11270,7 @@ async function saveSettings(andClose){
const showTps=!!($('settingsShowTps')||{}).checked; const showTps=!!($('settingsShowTps')||{}).checked;
const fadeTextEffect=!!($('settingsFadeTextEffect')||{}).checked; const fadeTextEffect=!!($('settingsFadeTextEffect')||{}).checked;
const showCliSessions=!!($('settingsShowCliSessions')||{}).checked; const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
const showClaudeCodeSessions=!!($('settingsShowClaudeCodeSessions')||{}).checked;
const showCronSessions=!!($('settingsShowCronSessions')||{}).checked; const showCronSessions=!!($('settingsShowCronSessions')||{}).checked;
const showWebhookSessions=!!($('settingsShowWebhookSessions')||{}).checked; const showWebhookSessions=!!($('settingsShowWebhookSessions')||{}).checked;
const showPreviousMessagingSessions=!!($('settingsShowPreviousMessagingSessions')||{}).checked; const showPreviousMessagingSessions=!!($('settingsShowPreviousMessagingSessions')||{}).checked;
@@ -11304,6 +11318,8 @@ async function saveSettings(andClose){
body.workspace_todos_tab=!!window._workspaceTodosTab; body.workspace_todos_tab=!!window._workspaceTodosTab;
body.api_redact_enabled=!!($('settingsApiRedact')||{}).checked; body.api_redact_enabled=!!($('settingsApiRedact')||{}).checked;
body.show_cli_sessions=showCliSessions; body.show_cli_sessions=showCliSessions;
// Persist the opt-out child independently; the read path applies the parent gate.
body.show_claude_code_sessions=showClaudeCodeSessions;
// Cron and webhook sessions are gated on CLI sessions (server short-circuits otherwise); // Cron and webhook sessions are gated on CLI sessions (server short-circuits otherwise);
// mirror the autosave path so the explicit Save Settings button persists them too. (#3514) // mirror the autosave path so the explicit Save Settings button persists them too. (#3514)
body.show_cron_sessions=showCliSessions&&showCronSessions; body.show_cron_sessions=showCliSessions&&showCronSessions;
+3 -2
View File
@@ -3,9 +3,9 @@
Mirrors the structure of test_1003_appearance_autosave.py to verify the Mirrors the structure of test_1003_appearance_autosave.py to verify the
preferences-panel autosave pattern is wired correctly: preferences-panel autosave pattern is wired correctly:
- All 15 preference fields use _schedulePreferencesAutosave (not _markSettingsDirty) - All preference fields use _schedulePreferencesAutosave (not _markSettingsDirty)
- Password field MUST still call _markSettingsDirty (security: never autosave) - Password field MUST still call _markSettingsDirty (security: never autosave)
- _preferencesPayloadFromUi covers all 15 fields - _preferencesPayloadFromUi covers all fields
- _setPreferencesAutosaveStatus uses the shared i18n keys - _setPreferencesAutosaveStatus uses the shared i18n keys
- Status div exists in static/index.html - Status div exists in static/index.html
- _autosavePreferencesSettings clears the dirty flag and hides the unsaved bar - _autosavePreferencesSettings clears the dirty flag and hides the unsaved bar
@@ -41,6 +41,7 @@ PREFERENCE_FIELDS_AUTOSAVE = [
("settingsShowConversationOutline", "show_conversation_outline"), ("settingsShowConversationOutline", "show_conversation_outline"),
("settingsShowTps", "show_tps"), ("settingsShowTps", "show_tps"),
("settingsShowCliSessions", "show_cli_sessions"), ("settingsShowCliSessions", "show_cli_sessions"),
("settingsShowClaudeCodeSessions", "show_claude_code_sessions"),
("settingsShowPreviousMessagingSessions", "show_previous_messaging_sessions"), ("settingsShowPreviousMessagingSessions", "show_previous_messaging_sessions"),
("settingsSyncInsights", "sync_to_insights"), ("settingsSyncInsights", "sync_to_insights"),
("settingsCheckUpdates", "check_for_updates"), ("settingsCheckUpdates", "check_for_updates"),
@@ -0,0 +1,488 @@
"""Regression coverage for Issue #4714: hide Claude Code imports independently.
The route must keep `show_cli_sessions` as the parent gate while allowing
`show_claude_code_sessions` to filter only Claude Code rows.
"""
import io
import json
import subprocess
from pathlib import Path
from urllib.parse import urlparse
import api.routes as routes
import api.models as models
import api.profiles as profiles
import pytest
ROOT = Path(__file__).resolve().parents[1]
PANELS_JS = ROOT / "static" / "panels.js"
INDEX_HTML = ROOT / "static" / "index.html"
class _FakeHandler:
def __init__(self):
self.status = None
self.headers = {}
self.wfile = io.BytesIO()
def send_response(self, status):
self.status = status
def send_header(self, key, value):
self.headers[key] = value
def end_headers(self):
pass
def json_body(self):
return json.loads(self.wfile.getvalue().decode("utf-8"))
def _handle_sessions(url):
handler = _FakeHandler()
routes.handle_get(handler, urlparse(url))
return handler
@pytest.fixture(autouse=True)
def _clear_cache():
routes._session_list_cache_clear()
models.clear_cli_sessions_cache()
yield
routes._session_list_cache_clear()
models.clear_cli_sessions_cache()
def _common_monkeypatches(monkeypatch, rows, cli_rows):
monkeypatch.setattr(routes, "load_settings", lambda: {
"show_cli_sessions": True,
"show_previous_messaging_sessions": False,
"show_cron_sessions": False,
})
monkeypatch.setattr(routes, "all_sessions", lambda diag=None: list(rows))
monkeypatch.setattr(routes, "_reconcile_stale_stream_state_for_session_rows", lambda _rows: False)
monkeypatch.setattr(routes, "_enrich_sidebar_lineage_metadata", lambda rows: None)
monkeypatch.setattr(routes, "agent_session_rows_existing", lambda ids, profile=None: {row["session_id"] for row in rows})
monkeypatch.setattr(routes, "get_cli_sessions", lambda source_filter=None, all_profiles=False, include_claude_code=True: cli_rows)
monkeypatch.setattr(profiles, "get_active_profile_name", lambda: "default")
def _extract_between(source: str, start_marker: str, end_marker: str) -> str:
start = source.index(start_marker)
end = source.index(end_marker, start)
return source[start:end]
def _rows_webui():
return [
{
"session_id": "webui-1",
"title": "WebUI",
"profile": "default",
"archived": False,
"message_count": 5,
"updated_at": 1000,
"last_message_at": 1000,
"source": "webui",
"raw_source": "webui",
"session_source": "webui",
"source_tag": "webui",
"is_cli_session": False,
}
]
def _row(session_id, source, raw_source, source_tag="cli", source_label=None, is_cli_session=True):
return {
"session_id": session_id,
"title": f"{session_id} title",
"profile": "default",
"archived": False,
"message_count": 2,
"updated_at": 2000,
"last_message_at": 2000,
"source": source,
"raw_source": raw_source,
"session_source": raw_source,
"source_tag": source_tag,
"source_label": source_label or source,
"is_cli_session": is_cli_session,
}
def test_show_cli_sessions_false_hides_all_imported_rows(monkeypatch):
"""When the parent toggle is off, no imported rows appear."""
rows = _rows_webui()
cli_rows = [_row("external-cli", "cli", "cli"), _row("external-claude", "cli", "claude_code")]
_common_monkeypatches(monkeypatch, rows, cli_rows)
monkeypatch.setattr(routes, "load_settings", lambda: {
"show_cli_sessions": False,
"show_claude_code_sessions": False,
"show_previous_messaging_sessions": False,
"show_cron_sessions": False,
})
handler = _handle_sessions("http://example.com/api/sessions")
body = handler.json_body()
assert handler.status == 200
assert len(body["sessions"]) == 1
assert body["sessions"][0]["session_id"] == "webui-1"
assert body["cli_count"] == 0
def test_show_claude_code_sessions_false_hides_only_claude_code_rows(monkeypatch):
"""Turning the Claude-specific toggle off should hide Claude Code while keeping
other imported rows."""
rows = _rows_webui()
all_cli_rows = [
_row("external-cli", "cli", "cli", source_tag="cli"),
_row("external-claude", "cli", "claude_code", source_tag="cli"),
]
include_claude = True
def fake_get_cli_sessions(source_filter=None, all_profiles=False, include_claude_code=True):
nonlocal include_claude
include_claude = include_claude_code
if include_claude_code:
return list(all_cli_rows)
return [row for row in all_cli_rows if row["raw_source"] != "claude_code"]
_common_monkeypatches(monkeypatch, rows, [])
monkeypatch.setattr(routes, "get_cli_sessions", fake_get_cli_sessions)
monkeypatch.setattr(routes, "load_settings", lambda: {
"show_cli_sessions": True,
"show_claude_code_sessions": False,
"show_previous_messaging_sessions": False,
"show_cron_sessions": False,
})
handler = _handle_sessions("http://example.com/api/sessions")
body = handler.json_body()
assert handler.status == 200
assert include_claude is False
assert {row["session_id"] for row in body["sessions"]} == {"webui-1", "external-cli"}
def test_show_claude_code_sessions_true_keeps_claude_code_rows_visible(monkeypatch):
"""With both toggles enabled, Claude Code rows are visible."""
rows = _rows_webui()
all_cli_rows = [
_row("external-cli", "cli", "cli"),
_row("external-claude", "cli", "claude_code"),
]
_common_monkeypatches(monkeypatch, rows, all_cli_rows)
monkeypatch.setattr(routes, "load_settings", lambda: {
"show_cli_sessions": True,
"show_claude_code_sessions": True,
"show_previous_messaging_sessions": False,
"show_cron_sessions": False,
})
handler = _handle_sessions("http://example.com/api/sessions")
body = handler.json_body()
assert handler.status == 200
assert {row["session_id"] for row in body["sessions"]} == {
"webui-1",
"external-cli",
"external-claude",
}
def test_session_list_cache_key_changes_with_claude_code_toggle():
"""Cache keys must encode the Claude Code toggle."""
key_false = routes._session_list_cache_key(
active_profile="default",
all_profiles=False,
show_cli_sessions=True,
show_claude_code_sessions=False,
show_previous_messaging_sessions=False,
show_cron_sessions=False,
)
key_true = routes._session_list_cache_key(
active_profile="default",
all_profiles=False,
show_cli_sessions=True,
show_claude_code_sessions=True,
show_previous_messaging_sessions=False,
show_cron_sessions=False,
)
assert key_false != key_true
def test_session_list_cache_key_default_keeps_claude_code_enabled():
"""Helper callers that omit the flag should match the config default."""
key_default = routes._session_list_cache_key(
active_profile="default",
all_profiles=False,
show_cli_sessions=True,
show_previous_messaging_sessions=False,
show_cron_sessions=False,
)
key_true = routes._session_list_cache_key(
active_profile="default",
all_profiles=False,
show_cli_sessions=True,
show_claude_code_sessions=True,
show_previous_messaging_sessions=False,
show_cron_sessions=False,
)
assert key_default == key_true
def test_cli_sessions_cache_key_varies_with_claude_code_toggle(monkeypatch):
"""The lower CLI-session cache must also key on the Claude Code toggle."""
calls = []
def fake_resolve(source_filter=None):
return Path("D:/tmp/hermes"), Path("D:/tmp/hermes/state.db"), "default", ("ctx", source_filter or "")
def fake_load(_home, _db_path, _profile, **kwargs):
include = kwargs["include_claude_code"]
calls.append(include)
return [{"session_id": "claude" if include else "plain"}]
monkeypatch.setattr(models, "_resolve_cli_sessions_context", fake_resolve)
monkeypatch.setattr(models, "_load_cli_sessions_uncached", fake_load)
monkeypatch.setattr(models, "_cli_sessions_cache_ttl_seconds", lambda: 60.0)
monkeypatch.setattr(models, "_cli_sessions_streaming_freeze_marker", lambda: None)
visible = models.get_cli_sessions(include_claude_code=True)
hidden = models.get_cli_sessions(include_claude_code=False)
assert calls == [True, False]
assert visible == [{"session_id": "claude"}]
assert hidden == [{"session_id": "plain"}]
def test_sessions_route_supports_historical_get_cli_sessions_signature(monkeypatch):
"""Route compatibility should not rely on swallowing internal TypeErrors."""
rows = _rows_webui()
_common_monkeypatches(monkeypatch, rows, [])
calls = []
def historical_get_cli_sessions(source_filter=None, all_profiles=False):
calls.append((source_filter, all_profiles))
return [_row("external-cli", "cli", "cli")]
monkeypatch.setattr(routes, "get_cli_sessions", historical_get_cli_sessions)
handler = _handle_sessions("http://example.com/api/sessions")
body = handler.json_body()
assert handler.status == 200
assert calls == [(None, False)]
assert {row["session_id"] for row in body["sessions"]} == {"webui-1", "external-cli"}
def test_all_profiles_scans_claude_code_only_once(monkeypatch):
"""All-profiles mode should keep the old single global Claude Code scan."""
calls = []
def fake_contexts():
return [
(Path("D:/tmp/profile-a"), Path("D:/tmp/profile-a/state.db"), "profile-a"),
(Path("D:/tmp/profile-b"), Path("D:/tmp/profile-b/state.db"), "profile-b"),
], ("profile-a", "profile-b")
def fake_load(_home, _db_path, profile, **kwargs):
include = kwargs["include_claude_code"]
calls.append((profile, include))
rows = [{"session_id": f"{profile}-cli"}]
if include:
rows.append({"session_id": "claude-global"})
return rows
monkeypatch.setattr(models, "_all_profiles_cli_contexts", fake_contexts)
monkeypatch.setattr(models, "_load_cli_sessions_uncached", fake_load)
monkeypatch.setattr(models, "_cli_sessions_cache_ttl_seconds", lambda: 0.0)
rows = models.get_cli_sessions(all_profiles=True, include_claude_code=True)
assert calls == [("profile-a", True), ("profile-b", False)]
assert [row["session_id"] for row in rows] == [
"profile-a-cli",
"claude-global",
"profile-b-cli",
]
def test_preferences_autosave_preserves_claude_code_opt_out_default():
"""Autosave must not stomp the opt-out child when the parent is off."""
autosave_block = _extract_between(
PANELS_JS.read_text(encoding="utf-8"),
" const showCliCb=$('settingsShowCliSessions');",
" const syncCb=$('settingsSyncInsights');",
)
script = f"""
const block = {json.dumps(autosave_block)};
const elements = {{
settingsShowCliSessions: {{ checked: false }},
settingsShowClaudeCodeSessions: {{ checked: true }},
settingsShowCronSessions: {{ checked: true }},
settingsShowPreviousMessagingSessions: {{ checked: false }},
}};
function $(id) {{
return elements[id] || null;
}}
const payload = {{}};
eval(block);
console.log(JSON.stringify(payload));
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
payload = json.loads(result.stdout)
assert payload["show_cli_sessions"] is False
assert payload["show_claude_code_sessions"] is True
def test_claude_code_checkbox_is_parent_gated_in_ui():
"""The child checkbox should disable live when the parent turns off."""
settings_block = _extract_between(
PANELS_JS.read_text(encoding="utf-8"),
" const showCliCb=$('settingsShowCliSessions');",
" const showPreviousMessagingCb=$('settingsShowPreviousMessagingSessions');",
)
script = f"""
const block = {json.dumps(settings_block)};
function makeCheckbox(checked) {{
return {{
checked,
disabled: false,
listeners: {{}},
addEventListener(name, fn) {{
this.listeners[name] = fn;
}},
}};
}}
const elements = {{
settingsShowCliSessions: makeCheckbox(true),
settingsShowClaudeCodeSessions: makeCheckbox(true),
settingsShowCronSessions: makeCheckbox(true),
}};
const settings = {{
show_cli_sessions: true,
show_claude_code_sessions: true,
show_cron_sessions: true,
}};
let autosaveCalls = 0;
function _schedulePreferencesAutosave() {{
autosaveCalls += 1;
}}
function $(id) {{
return elements[id] || null;
}}
eval(block);
const initial = {{
claudeDisabled: elements.settingsShowClaudeCodeSessions.disabled,
cronDisabled: elements.settingsShowCronSessions.disabled,
}};
elements.settingsShowCliSessions.checked = false;
elements.settingsShowCliSessions.listeners.change();
console.log(JSON.stringify({{
initial,
after: {{
claudeDisabled: elements.settingsShowClaudeCodeSessions.disabled,
cronDisabled: elements.settingsShowCronSessions.disabled,
autosaveCalls,
}},
}}));
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
payload = json.loads(result.stdout)
assert payload["initial"] == {
"claudeDisabled": False,
"cronDisabled": False,
}
assert payload["after"] == {
"claudeDisabled": True,
"cronDisabled": True,
"autosaveCalls": 1,
}
def test_claude_code_checkbox_parent_listener_does_not_depend_on_cron_checkbox():
"""The Claude child checkbox must still follow the parent without the cron node."""
settings_block = _extract_between(
PANELS_JS.read_text(encoding="utf-8"),
" const showCliCb=$('settingsShowCliSessions');",
" const showPreviousMessagingCb=$('settingsShowPreviousMessagingSessions');",
)
script = f"""
const block = {json.dumps(settings_block)};
function makeCheckbox(checked) {{
return {{
checked,
disabled: false,
listeners: {{}},
addEventListener(name, fn) {{
this.listeners[name] = fn;
}},
}};
}}
const elements = {{
settingsShowCliSessions: makeCheckbox(true),
settingsShowClaudeCodeSessions: makeCheckbox(true),
}};
const settings = {{
show_cli_sessions: true,
show_claude_code_sessions: true,
}};
let autosaveCalls = 0;
function _schedulePreferencesAutosave() {{
autosaveCalls += 1;
}}
function $(id) {{
return elements[id] || null;
}}
eval(block);
elements.settingsShowCliSessions.checked = false;
elements.settingsShowCliSessions.listeners.change();
console.log(JSON.stringify({{
claudeDisabled: elements.settingsShowClaudeCodeSessions.disabled,
autosaveCalls,
}}));
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
payload = json.loads(result.stdout)
assert payload == {
"claudeDisabled": True,
"autosaveCalls": 1,
}
def test_save_settings_preserves_claude_code_opt_out_default():
"""Explicit save must not persist the opt-out child as false via the parent gate."""
save_block = _extract_between(
PANELS_JS.read_text(encoding="utf-8"),
" body.show_cli_sessions=showCliSessions;",
" body.pinned_sessions_limit=pinnedSessionsLimit;",
)
script = f"""
const block = {json.dumps(save_block)};
const showCliSessions = false;
const showClaudeCodeSessions = true;
const showCronSessions = true;
const showPreviousMessagingSessions = false;
const body = {{}};
eval(block);
console.log(JSON.stringify(body));
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
body = json.loads(result.stdout)
assert body["show_cli_sessions"] is False
assert body["show_claude_code_sessions"] is True
def test_locale_keys_exist_in_every_locale_block():
"""Every locale block should carry the Claude Code label and description keys."""
i18n = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
assert i18n.count("settings_label_claude_code_sessions:") == i18n.count("settings_label_api_redact:")
assert i18n.count("settings_desc_claude_code_sessions:") == i18n.count("settings_desc_previous_messaging_sessions:")