feat: add Webhooks sidebar filter parity
Co-authored-by: Kilian Tyler <kilian@kil.dev>
This commit is contained in:
1 parent
0a1cf564c7
commit
5263b9acf7
14 files changed
+471
-41
No files matched your search
+155
-15
@@ -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),
|
||||
)
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user