feat: add Webhooks sidebar filter parity

Co-authored-by: Kilian Tyler <kilian@kil.dev>
This commit is contained in:
Maude BotandKilian Tyler authored and nesquena-hermes committed 2026-06-30 17:06:31 +00:00
1 parent 0a1cf564c7
commit 5263b9acf7
14 files changed
+471 -41

No files matched your search

+4 -1
View File
@@ -32,6 +32,7 @@ SOURCE_LABELS = {
'telegram': 'Telegram',
'tool': 'Tool',
'tui': 'TUI',
'webhook': 'Webhook',
'webui': 'WebUI',
'weixin': 'Weixin',
}
@@ -54,6 +55,8 @@ def normalize_agent_session_source(raw_source: str | None) -> dict:
session_source = 'messaging'
elif raw == 'cron':
session_source = 'cron'
elif raw == 'webhook':
session_source = 'webhook'
elif raw == 'tool':
session_source = 'tool'
elif raw == 'api_server':
@@ -173,7 +176,7 @@ def is_cli_session_row(row: dict) -> bool:
source_label = _safe_lower(row.get("source_label"))
if "webui" in {source, source_tag, raw_source, source_name, source_label}:
return False
non_cli_sources = MESSAGING_SOURCES | {"cron", "tool", "api", "api_server"}
non_cli_sources = MESSAGING_SOURCES | {"cron", "webhook", "tool", "api", "api_server"}
if {source, source_tag, raw_source, source_name, source_label} & non_cli_sources:
return False
if source == "messaging":
+2
View File
@@ -7802,6 +7802,7 @@ _SETTINGS_DEFAULTS = {
"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_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_previous_messaging_sessions": False, # show older Telegram/Discord/etc. reset segments
"sync_to_insights": False, # mirror WebUI token usage to state.db for /insights
"check_for_updates": True, # check if webui/agent repos are behind upstream
@@ -8046,6 +8047,7 @@ _SETTINGS_BOOL_KEYS = {
"fade_text_effect",
"show_cli_sessions",
"show_cron_sessions",
"show_webhook_sessions",
"show_previous_messaging_sessions",
"sync_to_insights",
"check_for_updates",
+155 -15
View File
@@ -37,6 +37,7 @@ CLI_VISIBLE_SESSION_LIMIT = 20
# addressable even when many newer non-cron sessions dominate the default
# sidebar window (#3172).
CRON_PROJECT_CHIP_LIMIT = 200
WEBHOOK_PROJECT_CHIP_LIMIT = 200
_CLI_SESSIONS_CACHE_TTL_SECONDS = 5.0
# While a turn is actively streaming, hold the CLI/cron projection longer than
# one poll interval (mirrors the route-level #4808 hold-down). The frontend
@@ -2906,12 +2907,19 @@ def new_session(workspace=None, model=None, profile=None, model_provider=None, p
s.save()
return s
def _hide_from_default_sidebar(session: dict, *, show_cron: bool = False) -> bool:
def _hide_from_default_sidebar(session: dict, *, show_cron: bool = False, show_webhook: bool = False) -> bool:
"""Return True for internal/background sessions hidden from the default list."""
sid = str(session.get('session_id') or '')
source = session.get('source_tag') or session.get('source')
source = (
session.get('source_tag')
or session.get('source')
or session.get('raw_source')
or session.get('session_source')
)
if not show_cron and (source == 'cron' or sid.startswith('cron_')):
return True
if not show_webhook and source == 'webhook':
return True
if bool(session.get('pre_compression_snapshot')):
return not bool(session.get('_show_pre_compression_snapshot'))
return False
@@ -2959,8 +2967,13 @@ def _has_live_sidebar_state(session: dict) -> bool:
def _is_intentionally_background_sidebar_session(session: dict) -> bool:
sid = str(session.get('session_id') or '')
source = session.get('source_tag') or session.get('source')
return source == 'cron' or sid.startswith('cron_')
source = (
session.get('source_tag')
or session.get('source')
or session.get('raw_source')
or session.get('session_source')
)
return source in {'cron', 'webhook'} or sid.startswith('cron_')
def _include_project_hidden_background_sidebar_sessions(
@@ -2969,9 +2982,9 @@ def _include_project_hidden_background_sidebar_sessions(
) -> list[dict]:
"""Keep project-assigned background sessions addressable by project chips.
Cron sessions stay hidden from the default sidebar, but if they have a
project assignment they must still be present in the client cache so the
dedicated project chip can reveal them (#3019).
Cron and webhook sessions stay hidden from the default sidebar, but if they
have a project assignment they must still be present in the client cache so
their dedicated project chips can reveal them (#3019).
"""
visible_ids = {
str(session.get('session_id'))
@@ -4149,7 +4162,43 @@ def ensure_cron_project() -> str:
return project_id
def is_cron_session(session_id: str, source_tag: str = None) -> bool:
WEBHOOK_PROJECT_NAME = 'Webhooks'
_WEBHOOK_PROJECT_LOCK = threading.Lock()
def ensure_webhook_project() -> str:
"""Return the project_id of the system "Webhooks" project for the active profile."""
from api.profiles import get_active_profile_name, _is_root_profile
active = get_active_profile_name() or 'default'
with _WEBHOOK_PROJECT_LOCK:
projects = load_projects()
for p in projects:
if p.get('name') != WEBHOOK_PROJECT_NAME:
continue
row_profile = p.get('profile')
if row_profile == active:
return p['project_id']
if _is_root_profile(row_profile or 'default') and _is_root_profile(active):
return p['project_id']
for p in projects:
if p.get('name') == WEBHOOK_PROJECT_NAME and not p.get('profile'):
p['profile'] = active
save_projects(projects)
return p['project_id']
project_id = uuid.uuid4().hex[:12]
projects.append({
'project_id': project_id,
'name': WEBHOOK_PROJECT_NAME,
'color': '#0ea5e9',
'profile': active,
'created_at': time.time(),
})
save_projects(projects)
return project_id
def is_cron_session(session_id: str, source_tag: str | None = None) -> bool:
"""Return True if a session originates from a cron job."""
if source_tag == 'cron':
return True
@@ -4157,6 +4206,11 @@ def is_cron_session(session_id: str, source_tag: str = None) -> bool:
return sid.startswith('cron_')
def is_webhook_session(session_id: str, source_tag: str | None = None) -> bool:
"""Return True if a session originates from a webhook route."""
return str(source_tag or '').strip().lower() == 'webhook'
def import_cli_session(
session_id: str,
@@ -4994,6 +5048,7 @@ def _load_cli_sessions_uncached(
*,
visible_session_limit: int | None = None,
cron_project_limit: int | None | bool = CRON_PROJECT_CHIP_LIMIT,
webhook_project_limit: int | None | bool = WEBHOOK_PROJECT_CHIP_LIMIT,
include_claude_code: bool = True,
) -> list:
cli_sessions = []
@@ -5061,12 +5116,29 @@ def _load_cli_sessions_uncached(
_cli_workspace_cache[0] = str(get_last_workspace())
return _cli_workspace_cache[0]
_webhook_pid_cache: list[str | None] = [None]
def _webhook_pid():
if _webhook_pid_cache[0] is None:
_webhook_pid_cache[0] = ensure_webhook_project()
return _webhook_pid_cache[0]
def _state_row_project_id(sid: str, source: str | None) -> str | None:
if is_cron_session(sid, source):
return _cron_pid()
if is_webhook_session(sid, source):
return _webhook_pid()
return None
profile_value = _cli_profile or 'default'
for row in read_importable_agent_session_rows(
db_path,
limit=visible_session_limit if visible_session_limit is not None else (CRON_PROJECT_CHIP_LIMIT if source_filter == 'cron' else CLI_VISIBLE_SESSION_LIMIT),
limit=visible_session_limit if visible_session_limit is not None else (
CRON_PROJECT_CHIP_LIMIT if source_filter == 'cron'
else WEBHOOK_PROJECT_CHIP_LIMIT if source_filter == 'webhook'
else CLI_VISIBLE_SESSION_LIMIT
),
log=logger,
exclude_sources=("cron",) if source_filter is None else None,
exclude_sources=("cron", "webhook") if source_filter is None else None,
include_sources=None if source_filter is None else (source_filter,),
):
sid = row['id']
@@ -5076,6 +5148,7 @@ def _load_cli_sessions_uncached(
profile = profile_value # CLI DB has no profile column; use active profile
_source = row['source'] or 'cli'
_source_meta = normalize_agent_session_source(_source)
_title = row['title']
if not _title and _source == 'cron':
# Look up the human-friendly cron job name (cron_{job_id}_{ts}) from
@@ -5101,18 +5174,18 @@ def _load_cli_sessions_uncached(
'updated_at': raw_ts,
'pinned': False,
'archived': _archived,
'project_id': _cron_pid() if is_cron_session(sid, _source) else None,
'project_id': _state_row_project_id(sid, _source),
'profile': profile,
'source_tag': _source,
'raw_source': row.get('raw_source'),
'raw_source': row.get('raw_source') or _source_meta.get('raw_source'),
'user_id': row.get('user_id'),
'chat_id': row.get('chat_id') or row.get('origin_chat_id'),
'chat_type': row.get('chat_type'),
'thread_id': row.get('thread_id'),
'session_key': row.get('session_key'),
'platform': row.get('platform'),
'session_source': row.get('session_source'),
'source_label': row.get('source_label'),
'session_source': row.get('session_source') or _source_meta.get('session_source'),
'source_label': row.get('source_label') or _source_meta.get('source_label'),
'parent_session_id': row.get('parent_session_id'),
'parent_title': row.get('parent_title'),
'parent_source': row.get('parent_source'),
@@ -5124,7 +5197,7 @@ def _load_cli_sessions_uncached(
'_lineage_root_id': row.get('_lineage_root_id'),
'_lineage_tip_id': row.get('_lineage_tip_id'),
'_compression_segment_count': row.get('_compression_segment_count'),
'is_cli_session': is_cli_session_row(row),
'is_cli_session': is_cli_session_row({**row, **_source_meta}),
})
if source_filter is not None:
@@ -5202,6 +5275,72 @@ def _load_cli_sessions_uncached(
except Exception:
logger.debug("Cron project-chip second pass failed", exc_info=True)
# --- Second pass: fetch webhook sessions that may have been squeezed out
# of the default window. They stay hidden from the default sidebar but must
# remain addressable under the Webhooks project chip.
if webhook_project_limit is not False:
existing_sids = {s['session_id'] for s in cli_sessions}
try:
for row in read_importable_agent_session_rows(
db_path,
limit=webhook_project_limit,
log=logger,
exclude_sources=None,
include_sources=("webhook",),
):
sid = row['id']
if sid in existing_sids:
continue
_source = row['source'] or 'webhook'
if _source != 'webhook':
continue
_source_meta = normalize_agent_session_source(_source)
raw_ts = row['last_activity'] or row['started_at']
_title = row['title']
_sidecar_meta = _state_projection_sidecar_metadata(sid)
if _sidecar_meta.get('title'):
_title = _sidecar_meta['title']
_archived = bool(_sidecar_meta.get('archived'))
_display_title = _title or 'Webhook Session'
cli_sessions.append({
'session_id': sid,
'title': _display_title,
'workspace': str(get_last_workspace()),
'model': row['model'] or None,
'message_count': row['message_count'] or row['actual_message_count'] or 0,
'created_at': row['started_at'],
'updated_at': raw_ts,
'pinned': False,
'archived': _archived,
'project_id': _webhook_pid(),
'profile': profile_value,
'source_tag': 'webhook',
'raw_source': row.get('raw_source') or _source_meta.get('raw_source'),
'user_id': row.get('user_id'),
'chat_id': row.get('chat_id') or row.get('origin_chat_id'),
'chat_type': row.get('chat_type'),
'thread_id': row.get('thread_id'),
'session_key': row.get('session_key'),
'platform': row.get('platform'),
'session_source': row.get('session_source') or _source_meta.get('session_source'),
'source_label': row.get('source_label') or _source_meta.get('source_label'),
'parent_session_id': row.get('parent_session_id'),
'parent_title': row.get('parent_title'),
'parent_source': row.get('parent_source'),
'relationship_type': row.get('relationship_type'),
'_parent_lineage_root_id': row.get('_parent_lineage_root_id'),
'end_reason': row.get('end_reason'),
'actual_message_count': row.get('actual_message_count'),
'user_message_count': row.get('actual_user_message_count'),
'_lineage_root_id': row.get('_lineage_root_id'),
'_lineage_tip_id': row.get('_lineage_tip_id'),
'_compression_segment_count': row.get('_compression_segment_count'),
'is_cli_session': is_cli_session_row({**row, **_source_meta}),
})
existing_sids.add(sid)
except Exception:
logger.debug("Webhook project-chip second pass failed", exc_info=True)
return cli_sessions
@@ -5247,6 +5386,7 @@ def get_cli_sessions(source_filter=None, *, all_profiles: bool = False) -> list:
source_filter=source_filter,
visible_session_limit=None,
cron_project_limit=None,
webhook_project_limit=None,
include_claude_code=(idx == 0),
)
)
+2
View File
@@ -125,6 +125,7 @@ def _session_list_cache_key(
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,
@@ -149,6 +150,7 @@ def _session_list_cache_key(
bool(include_archived),
bool(exclude_hidden),
bool(visible_only),
bool(show_webhook_sessions),
source_filter,
sidebar_source,
normalized_archived_limit,
+29 -8
View File
@@ -1802,6 +1802,7 @@ def _build_session_list_cache_payload(
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,
@@ -1859,6 +1860,7 @@ def _build_session_list_cache_payload(
show_cli_sessions = bool(show_cli_sessions)
show_previous_messaging_sessions = bool(show_previous_messaging_sessions)
show_cron_sessions = bool(show_cron_sessions)
show_webhook_sessions = bool(show_webhook_sessions)
webui_sessions = [_normalize_sidebar_source_flags(s) for s in webui_sessions]
if show_cli_sessions:
diag_stage("get_cli_sessions")
@@ -1948,6 +1950,7 @@ def _build_session_list_cache_payload(
cli,
represented_webui_ids,
show_cron_sessions=show_cron_sessions,
show_webhook_sessions=show_webhook_sessions,
)
else:
diag_stage("filter_webui_sessions")
@@ -2081,6 +2084,7 @@ def _build_session_list_cache_payload(
"show_cli_sessions": show_cli_sessions,
"show_previous_messaging_sessions": show_previous_messaging_sessions,
"show_cron_sessions": show_cron_sessions,
"show_webhook_sessions": show_webhook_sessions,
},
}
@@ -7389,16 +7393,22 @@ def _is_duplicate_webui_state_projection(session: dict, represented_webui_ids: s
return bool(_session_lineage_ids(session) & represented_webui_ids)
def _dedupe_cli_sidebar_sessions_for_api(cli: list[dict], represented_webui_ids: set[str], *, show_cron_sessions: bool = False) -> list[dict]:
"""Return CLI/state sidebar rows while preserving project-hidden cron rows.
def _dedupe_cli_sidebar_sessions_for_api(
cli: list[dict],
represented_webui_ids: set[str],
*,
show_cron_sessions: bool = False,
show_webhook_sessions: bool = False,
) -> list[dict]:
"""Return state sidebar rows while preserving project-hidden background rows.
Agent-side cron sessions come from state.db rather than the WebUI session
store. They should stay hidden from the default sidebar, but project-assigned
messageful rows must remain in the `/api/sessions` payload with
`default_hidden` so the matching project chip can reveal them (#3134).
Agent-side cron and webhook sessions come from state.db rather than the WebUI
session store. They should stay hidden from the default sidebar, but
project-assigned messageful rows must remain in the `/api/sessions` payload
with `default_hidden` so the matching project chip can reveal them (#3134).
"""
from api.models import (
_hide_from_default_sidebar as _cron_hide,
_hide_from_default_sidebar as _hide_background,
_include_project_hidden_background_sidebar_sessions,
)
@@ -7408,7 +7418,14 @@ def _dedupe_cli_sidebar_sessions_for_api(cli: list[dict], represented_webui_ids:
and not _is_duplicate_webui_state_projection(s, represented_webui_ids)
and is_cli_session_row_visible(s)
]
visible = [s for s in candidates if not _cron_hide(s, show_cron=show_cron_sessions)]
visible = [
s for s in candidates
if not _hide_background(
s,
show_cron=show_cron_sessions,
show_webhook=show_webhook_sessions,
)
]
return _include_project_hidden_background_sidebar_sessions(candidates, visible)
@@ -10917,6 +10934,7 @@ def handle_get(handler, parsed) -> bool:
settings.get("show_previous_messaging_sessions")
)
show_cron_sessions = bool(settings.get("show_cron_sessions"))
show_webhook_sessions = bool(settings.get("show_webhook_sessions"))
agent_session_source_filter = settings.get("agent_session_source_filter")
active_profile = get_active_profile_name()
all_profiles = _all_profiles_enabled(parsed)
@@ -10938,6 +10956,7 @@ def handle_get(handler, parsed) -> bool:
include_archived=include_archived,
exclude_hidden=exclude_hidden,
visible_only=True,
show_webhook_sessions=show_webhook_sessions,
source_filter=agent_session_source_filter,
sidebar_source=sidebar_source,
archived_limit=archived_limit,
@@ -10958,6 +10977,7 @@ def handle_get(handler, parsed) -> bool:
include_archived=include_archived,
exclude_hidden=exclude_hidden,
visible_only=True,
show_webhook_sessions=show_webhook_sessions,
source_filter=agent_session_source_filter,
sidebar_source=sidebar_source,
archived_limit=archived_limit,
@@ -13140,6 +13160,7 @@ def handle_post(handler, parsed) -> bool:
for k in (
"show_cli_sessions",
"show_cron_sessions",
"show_webhook_sessions",
"show_previous_messaging_sessions",
)
):
+28
View File
@@ -827,6 +827,7 @@ const LOCALES = {
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Show non-WebUI sessions',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Show previous messaging sessions',
settings_label_sync_insights: 'Sync to insights',
settings_label_check_updates: 'Check for updates',
@@ -1145,6 +1146,7 @@ const LOCALES = {
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_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_previous_messaging_sessions: 'Show older Discord, Telegram, Slack, and Weixin sessions that were replaced by reset or compression.',
settings_desc_sync_insights: 'Mirrors WebUI token usage to state.db so hermes /insights includes browser session data. Off by default.',
settings_desc_check_updates: 'Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.',
@@ -2468,6 +2470,7 @@ const LOCALES = {
cmd_reasoning: 'Mostra/nascondi ragionamento, imposta livello sforzo o controlla stato attuale',
settings_label_external_sessions: 'Mostra sessioni non-WebUI',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Mostra sessioni di messaggistica precedenti',
settings_label_sync_insights: 'Sincronizza con insights',
settings_label_check_updates: 'Verifica aggiornamenti',
@@ -2774,6 +2777,7 @@ const LOCALES = {
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_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_previous_messaging_sessions: 'Mostra sessioni Discord, Telegram, Slack e Weixin più vecchie sostituite da reset o compressione.',
settings_desc_sync_insights: 'Rispecchia l\'uso token WebUI su state.db così hermes /insights include i dati delle sessioni browser. Disattivato per impostazione predefinita.',
settings_desc_check_updates: 'Mostra un banner quando sono disponibili versioni più recenti della WebUI o dell\'Agente. Esegue un git fetch in background periodicamente.',
@@ -4100,6 +4104,7 @@ const LOCALES = {
cmd_reasoning: '思考表示の切り替え (表示/非表示)、努力レベル設定、現在状態の確認',
settings_label_external_sessions: '非WebUIセッションを表示',
settings_label_cron_sessions: 'Cronセッションを表示',
settings_label_webhook_sessions: 'Webhookセッションを表示',
settings_label_previous_messaging_sessions: '以前のメッセージングセッションを表示',
settings_label_sync_insights: 'インサイトに同期',
settings_label_check_updates: 'アップデートを確認',
@@ -4411,6 +4416,7 @@ const LOCALES = {
settings_desc_auto_title_refresh: '最新のやり取りに基づいてセッションタイトルを自動再生成し、会話の進行に合わせて適切に保ちます。LLM タイトル生成モデルの設定が必要です。',
settings_desc_external_sessions: 'CLI、Telegram、Discord、Slack その他のチャネルからの会話をセッション一覧に表示します。クリックでインポートして続行できます。',
settings_desc_cron_sessions: 'Cronジョブの出力をサイドバーの会話として表示します。WebUI以外のセッションが有効な場合のみ機能します。デフォルトはオフ。高頻度のジョブはサイドバーを溢れさせる可能性があります。',
settings_desc_webhook_sessions: 'Webhook実行をサイドバーの会話として表示します。WebUI以外のセッションが有効な場合のみ機能します。デフォルトはオフ。高頻度のルートはサイドバーを溢れさせる可能性があります。',
settings_desc_previous_messaging_sessions: 'reset または compression によって置き換えられた以前の Discord、Telegram、Slack、Weixin セッションを表示します。',
settings_desc_sync_insights: 'WebUI のトークン使用量を state.db にミラーし、hermes /insights にブラウザセッションのデータを含めます。デフォルトはオフ。',
settings_desc_check_updates: 'WebUI または Agent の新しいバージョンが利用可能な時にバナーを表示します。バックグラウンドで定期的に git fetch を実行します。',
@@ -5444,6 +5450,7 @@ const LOCALES = {
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Показывать внешние сеансы',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Показывать предыдущие сеансы обмена сообщениями',
settings_label_sync_insights: 'Синхронизировать с Insights',
settings_label_check_updates: 'Проверять обновления',
@@ -5680,6 +5687,7 @@ const LOCALES = {
settings_desc_auto_title_refresh: 'Автоматически переформулирует заголовок сессии на основе последнего обмена, чтобы он оставался актуальным по мере развития беседы. Требует настроенную модель генерации заголовков.',
settings_desc_external_sessions: 'Показать беседы из CLI, Telegram, Discord, Slack и других каналов в списке сеансов. Нажмите для импорта и продолжения.',
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_previous_messaging_sessions: 'Показывать предыдущие сеансы Discord, Telegram, Slack и Weixin, замененные сбросом или сжатием.',
settings_desc_sync_insights: 'Синхронизирует использование токенов WebUI в state.db, чтобы Hermes /insights включал данные браузерных сеансов. Выключено по умолчанию.',
settings_desc_check_updates: 'Показывает баннер, когда доступны более новые версии WebUI или Agent. Периодически выполняет git fetch в фоне.',
@@ -7003,6 +7011,7 @@ const LOCALES = {
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Mostrar sesiones externas',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Mostrar sesiones de mensajería anteriores',
settings_label_sync_insights: 'Sincronizar con insights',
settings_label_check_updates: 'Buscar actualizaciones',
@@ -7250,6 +7259,7 @@ const LOCALES = {
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_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_previous_messaging_sessions: 'Mostrar sesiones antiguas de Discord, Telegram, Slack y Weixin reemplazadas por reset o compresión.',
settings_desc_sync_insights: 'Refleja el uso de tokens de la WebUI en state.db para que hermes /insights incluya datos de sesiones del navegador. Desactivado por defecto.',
settings_desc_check_updates: 'Muestra un banner cuando haya versiones más nuevas de la WebUI o del Agent. Ejecuta periódicamente un git fetch en segundo plano.',
@@ -8550,6 +8560,7 @@ const LOCALES = {
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: 'Externe Sitzungen anzeigen',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Vorherige Messaging-Sitzungen anzeigen',
settings_label_sync_insights: 'Mit Insights synchronisieren',
settings_label_check_updates: 'Nach Updates suchen',
@@ -8787,6 +8798,7 @@ const LOCALES = {
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_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_previous_messaging_sessions: 'Zeigt ältere Discord-, Telegram-, Slack- und Weixin-Sitzungen, die durch Reset oder Compression ersetzt wurden.',
settings_desc_sync_insights: 'Spiegelt den WebUI-Token-Verbrauch in die state.db, sodass hermes /insights Browser-Sitzungsdaten enthält. Standardmäßig aus.',
settings_desc_check_updates: 'Zeigt ein Banner an, wenn neuere Versionen der WebUI oder des Agenten verfügbar sind. Führt regelmäßig einen Git-Fetch im Hintergrund aus.',
@@ -10162,6 +10174,7 @@ const LOCALES = {
cmd_reasoning: '切换思维可见性(显示/隐藏)、设置工作强度或查看当前状态',
settings_label_external_sessions: '显示外部会话',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: '显示以前的消息会话',
settings_label_sync_insights: '同步到 insights',
settings_label_check_updates: '检查更新',
@@ -10484,6 +10497,7 @@ const LOCALES = {
settings_desc_auto_title_refresh: '基于最新对话自动重新生成会话标题,使其随对话发展保持相关。需要配置 LLM 标题生成模型。',
settings_desc_external_sessions: '在会话列表中显示来自 CLI、Telegram、Discord、Slack 等渠道的对话。点击可导入并继续对话。',
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_previous_messaging_sessions: '显示被 reset 或 compression 替换的较旧的 Discord、Telegram、Slack 和 Weixin 会话。',
settings_desc_sync_insights: '将 WebUI token 使用情况同步到 state.db,使 hermes /insights 包含浏览器会话数据。默认关闭。',
settings_desc_check_updates: '当有更新的 WebUI 或助手版本时显示横幅。会在后台定期执行 git fetch。',
@@ -12011,6 +12025,7 @@ const LOCALES = {
cmd_reasoning: '切換思考內容可見性(顯示/隱藏)、設定推理強度,或檢查目前狀態',
settings_label_external_sessions: '顯示外部對話',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: '顯示以前的訊息對話',
settings_label_sync_insights: '同步至 insights',
settings_label_check_updates: '檢查更新',
@@ -12325,6 +12340,7 @@ const LOCALES = {
settings_desc_auto_title_refresh: '根據最新對話自動重新產生對話標題,讓標題隨對話發展保持相關。需要設定 LLM 標題產生模型。',
settings_desc_external_sessions: '在對話清單中顯示來自 CLI、Telegram、Discord、Slack 等管道的對話。點選即可匯入並繼續對話。',
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_previous_messaging_sessions: '顯示被 reset 或 compression 替換的較舊的 Discord、Telegram、Slack 和 Weixin 對話。',
settings_desc_sync_insights: '將 WebUI token 用量鏡射到 state.db,讓 hermes /insights 納入瀏覽器工作階段資料。預設為關閉。',
settings_desc_check_updates: '有較新版 WebUI 或 Agent 時顯示橫幅。系統會定期在背景執行 git fetch。',
@@ -13538,6 +13554,7 @@ const LOCALES = {
cmd_reasoning: 'Alternar visibilidade do pensamento (mostrar/ocultar)',
settings_label_external_sessions: 'Mostrar sessões externas',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Mostrar sessões de mensagens anteriores',
settings_label_sync_insights: 'Sincronizar para insights',
settings_label_check_updates: 'Verificar atualizações',
@@ -13778,6 +13795,7 @@ const LOCALES = {
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_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_previous_messaging_sessions: 'Mostrar sessões antigas de Discord, Telegram, Slack e Weixin substituídas por reset ou compressão.',
settings_desc_sync_insights: 'Espelha uso de tokens para state.db.',
settings_desc_check_updates: 'Mostrar banner quando versões mais novas estiverem disponíveis.',
@@ -15069,6 +15087,7 @@ const LOCALES = {
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
settings_label_external_sessions: '외부 세션 표시',
settings_label_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: '이전 메시징 세션 표시',
settings_label_sync_insights: 'Insights에 동기화',
settings_label_check_updates: '업데이트 확인',
@@ -15308,6 +15327,7 @@ const LOCALES = {
settings_desc_auto_title_refresh: '최신 대화를 바탕으로 세션 제목을 자동으로 다시 생성해 대화가 진행되어도 제목을 관련 있게 유지합니다. LLM 제목 생성 모델 설정이 필요합니다.',
settings_desc_external_sessions: 'CLI, Telegram, Discord, Slack 및 기타 채널의 대화를 세션 목록에 표시합니다. 클릭하여 가져오고 계속하세요.',
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_previous_messaging_sessions: 'reset 또는 compression으로 교체된 이전 Discord, Telegram, Slack, Weixin 세션을 표시합니다.',
settings_desc_sync_insights: 'WebUI 토큰 사용량을 state.db에 반영하여 hermes /insights에 브라우저 세션 데이터가 포함되도록 합니다. 기본값은 꺼짐입니다.',
settings_desc_check_updates: 'WebUI 또는 Agent의 새 버전이 있으면 배너를 표시합니다. 백그라운드에서 주기적으로 git fetch를 실행합니다.',
@@ -16713,6 +16733,7 @@ const LOCALES = {
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_cron_sessions: 'Afficher les sessions cron',
settings_label_webhook_sessions: 'Afficher les sessions webhook',
settings_label_previous_messaging_sessions: 'Afficher les sessions de messagerie précédentes',
settings_label_sync_insights: 'Synchroniser avec les analyses',
settings_label_check_updates: 'Vérifier les mises à jour',
@@ -17020,6 +17041,7 @@ const LOCALES = {
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_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_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_sync_insights: 'Replique l\'utilisation des jetons de l\'interface Web vers state.db afin que hermes /insights inclue les données des sessions du navigateur. Désactivé par défaut.',
settings_desc_check_updates: 'Afficher une bannière lorsque de nouvelles versions de l\'interface Web ou de l\'agent sont disponibles. Exécute périodiquement un git fetch en arrière-plan.',
@@ -18313,6 +18335,7 @@ const LOCALES = {
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_cron_sessions: 'Show cron sessions',
settings_label_webhook_sessions: 'Show webhook sessions',
settings_label_previous_messaging_sessions: 'Önceki mesajlaşma oturumlarını göster',
settings_label_sync_insights: 'Analizlerle senkronize edin',
settings_label_check_updates: 'Güncellemeleri kontrol edin',
@@ -18551,6 +18574,7 @@ const LOCALES = {
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_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_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_sync_insights: 'WebUI belirteci kullanımını state.db\'ye yansıtır, böylece hermes /insights tarayıcı oturum verilerini içerir. Varsayılan olarak kapalıdır.',
settings_desc_check_updates: 'WebUI veya Agent\'ın daha yeni sürümleri mevcut olduğunda bir banner gösterin. Periyodik olarak bir arka plan git getirme işlemi çalıştırır.',
@@ -19959,6 +19983,7 @@ const LOCALES = {
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_cron_sessions: 'Pokaż sesje cron',
settings_label_webhook_sessions: 'Pokaż sesje webhooków',
settings_label_previous_messaging_sessions: 'Pokaż poprzednie sesje komunikatorów',
settings_label_sync_insights: 'Synchronizuj z Insights',
settings_label_check_updates: 'Sprawdzaj aktualizacje',
@@ -20267,6 +20292,7 @@ const LOCALES = {
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_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_previous_messaging_sessions: 'Pokaż starsze sesje z Discorda, Telegrama, Slacka i Weixin, które zostały zastąpione przez reset lub kompresję.',
settings_desc_sync_insights: 'Odzwierciedla zużycie tokenów WebUI w state.db, dzięki czemu hermes /insights uwzględnia dane sesji przeglądarki. Domyślnie wyłączone.',
settings_desc_check_updates: 'Pokaż baner, gdy dostępne są nowsze wersje WebUI lub Agenta. Okresowo uruchamia pobieranie git fetch w tle.',
@@ -21433,6 +21459,7 @@ const LOCALES = {
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_cron_sessions: 'Hiện phiên cron',
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_sync_insights: 'Đồng bộ sang insights',
settings_label_check_updates: 'Kiểm tra cập nhật',
@@ -21738,6 +21765,7 @@ const LOCALES = {
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_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_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_sync_insights: 'Đồng bộ token usage của WebUI sang state.db để hermes /insights bao gồm dữ liệu phiên trình duyệt. Mặc định tắt.',
settings_desc_check_updates: 'Hiển thị banner khi có phiên bản mới của WebUI hoặc Agent. Định kỳ chạy git fetch nền.',
+7
View File
@@ -1329,6 +1329,13 @@
</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>
<div class="settings-field" id="settingsShowWebhookSessionsField" style="margin-left:24px">
<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)">
<span data-i18n="settings_label_webhook_sessions">Show webhook sessions</span>
</label>
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="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.</div>
</div>
<div class="settings-field">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="settingsShowPreviousMessagingSessions" style="width:15px;height:15px;accent-color:var(--accent)">
+13 -2
View File
@@ -7408,6 +7408,8 @@ function _preferencesPayloadFromUi(){
// identically to the explicit saveSettings() path, so neither save route can
// persist show_cron_sessions=true while show_cli_sessions=false. (#3514)
if(showCronCb) payload.show_cron_sessions=!!(showCliCb&&showCliCb.checked&&showCronCb.checked);
const showWebhookCb=$('settingsShowWebhookSessions');
if(showWebhookCb) payload.show_webhook_sessions=!!(showCliCb&&showCliCb.checked&&showWebhookCb.checked);
const showPreviousMessagingCb=$('settingsShowPreviousMessagingSessions');
if(showPreviousMessagingCb) payload.show_previous_messaging_sessions=showPreviousMessagingCb.checked;
const syncCb=$('settingsSyncInsights');
@@ -7914,6 +7916,13 @@ async function loadSettingsPanel(){
showCronCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});
if(showCliCb){showCliCb.addEventListener('change',function(){showCronCb.disabled=!showCliCb.checked;},{once:false});}
}
const showWebhookCb=$('settingsShowWebhookSessions');
if(showWebhookCb){
showWebhookCb.checked=!!settings.show_webhook_sessions;
showWebhookCb.disabled=showCliCb?!showCliCb.checked:true;
showWebhookCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});
if(showCliCb){showCliCb.addEventListener('change',function(){showWebhookCb.disabled=!showCliCb.checked;},{once:false});}
}
const showPreviousMessagingCb=$('settingsShowPreviousMessagingSessions');
if(showPreviousMessagingCb){showPreviousMessagingCb.checked=!!settings.show_previous_messaging_sessions;showPreviousMessagingCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
const syncCb=$('settingsSyncInsights');
@@ -10535,6 +10544,7 @@ async function saveSettings(andClose){
const fadeTextEffect=!!($('settingsFadeTextEffect')||{}).checked;
const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
const showCronSessions=!!($('settingsShowCronSessions')||{}).checked;
const showWebhookSessions=!!($('settingsShowWebhookSessions')||{}).checked;
const showPreviousMessagingSessions=!!($('settingsShowPreviousMessagingSessions')||{}).checked;
const pinnedSessionsLimit=parseInt(($('settingsPinnedSessionsLimit')||{}).value,10)||3;
const pw=($('settingsPassword')||{}).value;
@@ -10575,9 +10585,10 @@ async function saveSettings(andClose){
body.workspace_todos_tab=!!window._workspaceTodosTab;
body.api_redact_enabled=!!($('settingsApiRedact')||{}).checked;
body.show_cli_sessions=showCliSessions;
// Cron sessions are gated on CLI sessions (server short-circuits otherwise);
// mirror the autosave path so the explicit Save Settings button persists it too. (#3514)
// 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)
body.show_cron_sessions=showCliSessions&&showCronSessions;
body.show_webhook_sessions=showCliSessions&&showWebhookSessions;
body.show_previous_messaging_sessions=showPreviousMessagingSessions;
body.pinned_sessions_limit=pinnedSessionsLimit;
body.sync_to_insights=!!($('settingsSyncInsights')||{}).checked;
+7 -7
View File
@@ -164,15 +164,15 @@ def test_get_cli_sessions_cache_invalidates_when_sqlite_wal_changes(monkeypatch,
Path(f"{db_path}-wal").write_text("new wal contents", encoding="utf-8")
second = models.get_cli_sessions()
# Two calls to get_cli_sessions() × 2 invocations each (first pass +
# second cron-only pass) = 4 total calls to the mock.
assert calls == 4
# Two calls to get_cli_sessions() × 3 invocations each (first pass +
# cron-only pass + webhook-only pass) = 6 total calls to the mock.
assert calls == 6
# First pass of first call returned message_count=1 (calls was 1).
assert first[0]["message_count"] == 1
# First pass of second call returned message_count=3 (calls was 3;
# the second pass incremented calls to 2 and 4 but cron-only filter
# excluded the cli-source session from both second passes).
assert second[0]["message_count"] == 3
# First pass of second call returned message_count=4 (calls was 4;
# source-specific passes incremented calls to 2, 3, 5, and 6 but excluded
# the cli-source session from those pass results).
assert second[0]["message_count"] == 4
def test_session_import_cli_returns_read_only_claude_code_payload(monkeypatch, tmp_path):
+1
View File
@@ -960,6 +960,7 @@ def test_agent_session_source_normalization_contract():
'discord': ('messaging', 'Discord'),
'slack': ('messaging', 'Slack'),
'cron': ('cron', 'Cron'),
'webhook': ('webhook', 'Webhook'),
'tool': ('tool', 'Tool'),
'api_server': ('api', 'API'),
'something_new': ('other', 'Something New'),
@@ -1,4 +1,4 @@
"""Tests for #2841: show_cron_sessions toggle to surface cron sessions in the sidebar."""
"""Tests for #2841: background session toggles surface hidden sessions in the sidebar."""
import pathlib
from api.models import _hide_from_default_sidebar
@@ -20,8 +20,19 @@ def test_cron_visible_when_show_cron_true():
assert _hide_from_default_sidebar({'source_tag': 'cron', 'session_id': 'cron_abc'}, show_cron=True) is False
def test_pre_compression_always_hidden_regardless_of_show_cron():
assert _hide_from_default_sidebar({'pre_compression_snapshot': True}, show_cron=True) is True
def test_webhook_hidden_by_default():
assert _hide_from_default_sidebar({'source_tag': 'webhook', 'session_id': 'webhook:route:run'}) is True
def test_webhook_visible_when_show_webhook_true():
assert _hide_from_default_sidebar(
{'source_tag': 'webhook', 'session_id': 'webhook:route:run'},
show_webhook=True,
) is False
def test_pre_compression_always_hidden_regardless_of_background_toggles():
assert _hide_from_default_sidebar({'pre_compression_snapshot': True}, show_cron=True, show_webhook=True) is True
def test_cron_hidden_with_explicit_false():
@@ -37,6 +48,13 @@ def test_show_cron_sessions_in_defaults():
)
def test_show_webhook_sessions_in_defaults():
src = _read("api/config.py")
assert '"show_webhook_sessions": False' in src, (
'"show_webhook_sessions": False must appear in _SETTINGS_DEFAULTS'
)
def test_show_cron_sessions_in_bool_keys():
src = _read("api/config.py")
assert '"show_cron_sessions"' in src, (
@@ -48,6 +66,16 @@ def test_show_cron_sessions_in_bool_keys():
)
def test_show_webhook_sessions_in_bool_keys():
src = _read("api/config.py")
assert '"show_webhook_sessions"' in src, (
'"show_webhook_sessions" must appear in _SETTINGS_BOOL_KEYS'
)
assert src.count('"show_webhook_sessions"') >= 2, (
'"show_webhook_sessions" must appear in both _SETTINGS_DEFAULTS and _SETTINGS_BOOL_KEYS'
)
# --- api/routes.py string-scan ---
def test_show_cron_sessions_kwarg_passthrough():
@@ -57,6 +85,22 @@ def test_show_cron_sessions_kwarg_passthrough():
)
def test_show_webhook_sessions_kwarg_passthrough():
src = _read("api/routes.py")
assert "show_webhook_sessions=show_webhook_sessions" in src, (
"show_webhook_sessions kwarg must be forwarded at the _dedupe_cli_sidebar_sessions_for_api call site"
)
def test_show_webhook_sessions_invalidates_session_cache_on_settings_save():
src = _read("api/routes.py")
invalidation_block = src.split("Settings that change which sessions appear in the sidebar", 1)[1]
invalidation_block = invalidation_block.split("auth_enabled_after", 1)[0]
assert '"show_webhook_sessions"' in invalidation_block, (
"settings POST must explicitly invalidate /api/sessions cache when show_webhook_sessions changes"
)
# --- static/index.html string-scan ---
def test_settings_show_cron_sessions_in_html():
@@ -66,19 +110,32 @@ def test_settings_show_cron_sessions_in_html():
)
def test_settings_show_webhook_sessions_in_html():
src = _read("static/index.html")
assert "settingsShowWebhookSessions" in src, (
"settingsShowWebhookSessions checkbox must appear in static/index.html"
)
# --- static/panels.js string-scans ---
def test_panels_save_wiring():
src = _read("static/panels.js")
# Both save paths (autosave _preferencesPayloadFromUi + explicit saveSettings)
# must gate cron sessions on the CLI-sessions checkbox so neither can persist
# show_cron_sessions=true while show_cli_sessions=false (#3514).
# must gate background sessions on the CLI-sessions checkbox so neither can
# persist true while show_cli_sessions=false (#3514).
assert "payload.show_cron_sessions=!!(showCliCb&&showCliCb.checked&&showCronCb.checked)" in src, (
"autosave wiring must gate show_cron_sessions on settingsShowCliSessions in static/panels.js"
)
assert "body.show_cron_sessions=showCliSessions&&showCronSessions" in src, (
"explicit saveSettings() must gate show_cron_sessions on showCliSessions in static/panels.js"
)
assert "payload.show_webhook_sessions=!!(showCliCb&&showCliCb.checked&&showWebhookCb.checked)" in src, (
"autosave wiring must gate show_webhook_sessions on settingsShowCliSessions in static/panels.js"
)
assert "body.show_webhook_sessions=showCliSessions&&showWebhookSessions" in src, (
"explicit saveSettings() must gate show_webhook_sessions on showCliSessions in static/panels.js"
)
def test_panels_load_wiring():
@@ -86,3 +143,6 @@ def test_panels_load_wiring():
assert "show_cron_sessions" in src, (
"load wiring for show_cron_sessions must appear in static/panels.js"
)
assert "show_webhook_sessions" in src, (
"load wiring for show_webhook_sessions must appear in static/panels.js"
)
@@ -136,9 +136,11 @@ def test_cron_sessions_recovered_by_second_pass(tmp_path):
cron_sessions = [s for s in result if s["source_tag"] == "cron"]
assert len(cron_sessions) > 0, "Cron sessions should be recovered by the second pass"
assert len(read_rows.call_args_list) == 2
assert len(read_rows.call_args_list) == 3
assert read_rows.call_args_list[1].kwargs["exclude_sources"] is None
assert read_rows.call_args_list[1].kwargs["include_sources"] == ("cron",)
assert read_rows.call_args_list[2].kwargs["exclude_sources"] is None
assert read_rows.call_args_list[2].kwargs["include_sources"] == ("webhook",)
def test_webui_sidecarless_sessions_not_excluded(tmp_path):
@@ -226,8 +226,9 @@ def test_webhook_state_projection_preserves_archived_sidecar(monkeypatch, tmp_pa
assert row["title"] == "Webhook Session"
assert row["source_tag"] == "webhook"
assert row["raw_source"] == "webhook"
assert row["session_source"] == "other"
assert row["session_source"] == "webhook"
assert row["source_label"] == "Webhook"
assert row["project_id"]
assert row["is_cli_session"] is False
assert row["archived"] is True
@@ -248,8 +249,9 @@ def test_archived_webhook_projection_reaches_sidebar_payload(monkeypatch):
"archived": True,
"source_tag": "webhook",
"raw_source": "webhook",
"session_source": "other",
"session_source": "webhook",
"source_label": "Webhook",
"project_id": "webhook-project",
"is_cli_session": False,
}
@@ -282,6 +284,7 @@ def test_archived_webhook_projection_reaches_sidebar_payload(monkeypatch):
assert len(matching) == 1
assert matching[0]["archived"] is True
assert matching[0]["source_tag"] == "webhook"
assert matching[0]["default_hidden"] is True
assert matching[0]["is_cli_session"] is False
+150
View File
@@ -0,0 +1,150 @@
"""Webhook sessions should behave like cron sessions in the project chip layer."""
def _agent_row(session_id="webhook-route-1", *, source="webhook", title="Webhook Run"):
return {
"id": session_id,
"title": title,
"model": "test-model",
"source": source,
"raw_source": source,
"message_count": 1,
"actual_message_count": 1,
"actual_user_message_count": 1,
"last_activity": 10.0,
"started_at": 9.0,
}
def test_webhook_source_normalizes_to_webhook_category():
from api.agent_sessions import is_cli_session_row, normalize_agent_session_source
normalized = normalize_agent_session_source("webhook")
assert normalized == {
"raw_source": "webhook",
"session_source": "webhook",
"source_label": "Webhook",
}
assert is_cli_session_row({**normalized, "source": "webhook", "title": "Webhook Session"}) is False
def test_ensure_webhook_project_creates_dedicated_project():
from api.models import WEBHOOK_PROJECT_NAME, ensure_webhook_project, load_projects, save_projects
projects = load_projects()
save_projects([p for p in projects if p.get("name") != WEBHOOK_PROJECT_NAME])
pid1 = ensure_webhook_project()
pid2 = ensure_webhook_project()
assert pid1 == pid2
projects = load_projects()
webhook_projects = [p for p in projects if p.get("name") == WEBHOOK_PROJECT_NAME]
assert len(webhook_projects) == 1
assert webhook_projects[0]["project_id"] == pid1
assert webhook_projects[0]["color"] == "#0ea5e9"
assert len(pid1) == 12
def test_is_webhook_session_detects_source_metadata_not_session_id():
from api.models import is_webhook_session
assert is_webhook_session("any-id", source_tag="webhook") is True
assert is_webhook_session("any-id", source_tag=" WebHook ") is True
assert is_webhook_session("webhook:route:delivery") is False
assert is_webhook_session("cron_abc", source_tag="cron") is False
assert is_webhook_session("regular-session", source_tag="cli") is False
assert is_webhook_session("") is False
def test_project_assigned_webhook_rows_are_returned_but_default_hidden():
from api.models import (
_hide_from_default_sidebar,
_include_project_hidden_background_sidebar_sessions,
)
assert _hide_from_default_sidebar({"source_tag": "webhook", "session_id": "webhook:route:1"}) is True
assert _hide_from_default_sidebar({"session_id": "webhook:route:1"}) is False
visible = [
{"session_id": "webui-1", "title": "Normal", "message_count": 1, "project_id": None},
]
candidates = visible + [
{
"session_id": "webhook-visible",
"source_tag": "webhook",
"title": "Webhook output",
"message_count": 2,
"project_id": "webhook-project",
},
{
"session_id": "webhook-unassigned",
"source_tag": "webhook",
"title": "Webhook output",
"message_count": 2,
"project_id": None,
},
{
"session_id": "webhook-empty",
"source_tag": "webhook",
"title": "Webhook output",
"message_count": 0,
"project_id": "webhook-project",
},
]
rows = _include_project_hidden_background_sidebar_sessions(candidates, visible)
by_id = {row["session_id"]: row for row in rows}
assert set(by_id) == {"webui-1", "webhook-visible"}
assert by_id["webhook-visible"]["default_hidden"] is True
def test_webhook_rows_get_webhook_project_id(monkeypatch, tmp_path):
import api.models as models
db = tmp_path / "state.db"
db.write_text("", encoding="utf-8")
monkeypatch.setattr(models, "read_importable_agent_session_rows", lambda *_a, **_kw: [_agent_row()])
monkeypatch.setattr(models, "get_last_workspace", lambda: tmp_path)
monkeypatch.setattr(models, "ensure_cron_project", lambda: "cron-project-id")
monkeypatch.setattr(models, "ensure_webhook_project", lambda: "webhook-project-id", raising=False)
monkeypatch.setattr(models.Session, "load_metadata_only", lambda _sid: None)
rows = models._load_cli_sessions_uncached(tmp_path, db, _cli_profile=None, cron_project_limit=False)
assert len(rows) == 1
assert rows[0]["source_tag"] == "webhook"
assert rows[0]["session_source"] == "webhook"
assert rows[0]["source_label"] == "Webhook"
assert rows[0]["project_id"] == "webhook-project-id"
assert rows[0]["is_cli_session"] is False
def test_webhook_second_pass_keeps_older_project_rows_available(monkeypatch, tmp_path):
import api.models as models
db = tmp_path / "state.db"
db.write_text("", encoding="utf-8")
calls = []
def fake_read_rows(_db_path, **kwargs):
calls.append(kwargs)
if kwargs.get("include_sources") == ("webhook",):
return [_agent_row("webhook-older", title="Older webhook")]
return []
monkeypatch.setattr(models, "read_importable_agent_session_rows", fake_read_rows)
monkeypatch.setattr(models, "get_last_workspace", lambda: tmp_path)
monkeypatch.setattr(models, "ensure_cron_project", lambda: "cron-project-id")
monkeypatch.setattr(models, "ensure_webhook_project", lambda: "webhook-project-id", raising=False)
monkeypatch.setattr(models.Session, "load_metadata_only", lambda _sid: None)
rows = models._load_cli_sessions_uncached(tmp_path, db, _cli_profile=None, cron_project_limit=False)
assert [call.get("include_sources") for call in calls] == [None, ("webhook",)]
assert len(rows) == 1
assert rows[0]["session_id"] == "webhook-older"
assert rows[0]["project_id"] == "webhook-project-id"