diff --git a/tui_gateway/session_auto_continue.py b/tui_gateway/session_auto_continue.py index 5a90068eab..ba694d100b 100644 --- a/tui_gateway/session_auto_continue.py +++ b/tui_gateway/session_auto_continue.py @@ -4,7 +4,6 @@ busy-submit handling. Bodies are rebound onto server.py's globals at install tim from __future__ import annotations - import contextlib from .method_ctx import bind_module @@ -125,7 +124,6 @@ def _maybe_schedule_auto_continue(sid: str, session: dict, session_key: str) -> except Exception as exc: _notif_log_failure("auto-continue dispatch failed", exc) _ac_release_turn(session) - threading.Thread(target=kickoff, daemon=True).start() logger.info("auto-continue scheduled for session %s (attempt %d, interrupted %.0fs ago)", session_key, attempt, age) return {"attempt": attempt, "interrupted_at": marker["started_at"]} @@ -222,7 +220,6 @@ def _interrupt_busy_session(sid: str, session: dict, agent: Any) -> None: finally: with session["history_lock"]: session["_busy_interrupt_pending"] = False - threading.Thread(target=interrupt, daemon=True, name=f"busy-interrupt-{sid}").start() @@ -375,7 +372,6 @@ def _emit_terminal_turn_error( if error_surface is None and isinstance(error, BaseException): with contextlib.suppress(Exception): from agent.error_surface import build_error_surface_from_exception - error_surface = build_error_surface_from_exception( error, provider=str(getattr(agent, "provider", "") or ""), model=str(getattr(agent, "model", "") or "") ) diff --git a/tui_gateway/session_compression.py b/tui_gateway/session_compression.py index a2e7a8836c..1fb2fb07e8 100644 --- a/tui_gateway/session_compression.py +++ b/tui_gateway/session_compression.py @@ -13,7 +13,6 @@ def _tui_compression_config_signature(cfg: dict | None) -> tuple: """Stable snapshot of compression/context keys that must apply next turn: the messaging-gateway cache-busting extract plus ``idle_compact_after_seconds``/``tail_mode`` (live-TUI-only keys).""" from gateway.run import GatewayRunner - keys = GatewayRunner._extract_cache_busting_config(cfg) picked = {k: v for k, v in keys.items() if k.startswith("compression.") or k == "model.context_length"} compression = cfg.get("compression") if isinstance(cfg, dict) and isinstance(cfg.get("compression"), dict) else {} @@ -27,7 +26,6 @@ def _compressor_ctor_default(name: str, fallback: Any) -> Any: try: import inspect from agent.context_compressor import ContextCompressor - default = inspect.signature(ContextCompressor.__init__).parameters[name].default return fallback if default is inspect.Parameter.empty else default except Exception: @@ -45,7 +43,6 @@ def _derived_default_threshold_percent(agent: Any, compression: dict) -> float: try: from agent.agent_init import _resolve_compression_threshold from agent.auxiliary_client import _compression_threshold_for_model, _is_codex_gpt54_or_gpt55, _is_codex_spark - model, provider = getattr(agent, "model", "") or "", getattr(agent, "provider", "") or "" autoraise_enabled = str(compression.get("codex_gpt55_autoraise", True)).lower() in {"true", "1", "yes"} pct, _notice = _resolve_compression_threshold( @@ -76,7 +73,6 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None: cfg = cfg if isinstance(cfg, dict) else {} compression = cfg.get("compression") if isinstance(cfg.get("compression"), dict) else {} model_cfg = cfg.get("model") if isinstance(cfg.get("model"), dict) else {} - enabled_raw = compression.get("enabled", True) agent.compression_enabled = enabled_raw if isinstance(enabled_raw, bool) else str(enabled_raw).lower() in {"true", "1", "yes"} agent.codex_responses_native_compaction = is_truthy_value(compression.get("codex_responses_native", False)) @@ -91,7 +87,6 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None: # Absence restores the agent_init/config default (0 = disabled). with contextlib.suppress(TypeError, ValueError): agent.compression_idle_compact_after_seconds = max(0, int(compression.get("idle_compact_after_seconds", 0) or 0)) - cc = getattr(agent, "context_compressor", None) if cc is None: return @@ -112,11 +107,9 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None: cc.model_thresholds = { str(k): float(v) for k, v in raw_thresholds.items() if isinstance(v, (int, float)) and not isinstance(v, bool) } if isinstance(raw_thresholds, dict) else {} - # threshold: present value wins; absence derives via the agent_init resolution (default + autoraise). # resolve_model_threshold returns ``pct`` unchanged when model_thresholds is empty. from agent.context_compressor import resolve_model_threshold - pct: float | None = None if "threshold" in compression: with contextlib.suppress(TypeError, ValueError): @@ -129,7 +122,6 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None: cc.threshold_percent = cc._effective_threshold_percent(cc.context_length, base) except Exception: cc.threshold_percent = pct - raw_ctx = model_cfg.get("context_length") if raw_ctx is not None: with contextlib.suppress(TypeError, ValueError): @@ -141,7 +133,6 @@ def _apply_live_compression_config(agent: Any, cfg: dict | None) -> None: # model.context_length removed: drop the override and force re-inference from model metadata on # next access (construction's deferred resolution); re-applies the small-context floor too. cc._config_context_length = cc._resolved_context_length = None - cc.threshold_tokens_cap = cc._coerce_threshold_tokens_cap(compression.get("threshold_tokens")) # Invalidate the cached trigger so the next preflight re-derives from percent/window, then the cap. cc._threshold_tokens = cc._tail_token_budget = None @@ -206,7 +197,6 @@ def _compress_session_history( from hermes_cli.partial_compress import ( parse_partial_compress_args, rejoin_compressed_head_and_tail, split_history_for_partial_compress, ) - agent = session["agent"] # Snapshot under the lock so the LLM-bound compression call does NOT hold history_lock for the # request — otherwise prompt.submit etc. block on the dispatcher loop while compaction runs. @@ -247,7 +237,6 @@ def _compress_session_history( # No boundary committed; discard the pending deferred notification (exactly-once, no-op safe). finalize_context_engine_compression_notification(agent, committed=False) raise CompressionLockHeld(_lock_skipped if isinstance(_lock_skipped, str) else None) - if tail: compressed = rejoin_compressed_head_and_tail(compressed, tail) with session["history_lock"]: @@ -273,18 +262,15 @@ def _sync_session_key_after_compress( old_key = session.get("session_key", "") or "" if not new_session_id or new_session_id == old_key: return - if not _transfer_active_session_slot(sid, session, new_session_id=new_session_id): logger.warning( "Compression session lease did not re-anchor: sid=%s old_session_id=%s new_session_id=%s", sid, old_key, new_session_id, ) - # Even if the approval module fails to import, anchor session_key on the continuation id. session["session_key"] = new_session_id with contextlib.suppress(Exception): from tools import approval - with contextlib.suppress(Exception): approval.unregister_gateway_notify(old_key) with contextlib.suppress(Exception): @@ -296,7 +282,6 @@ def _sync_session_key_after_compress( # Invalidate any in-flight ``_drain_queued_prompt`` claim taken under the pre-rotation key: a raced # drain must not dispatch on the continuation (its envelope is restored to the queue). session["_queued_prompt_generation"] = int(session.get("_queued_prompt_generation", 0)) + 1 - if clear_pending_title: session["pending_title"] = None if restart_slash_worker: diff --git a/tui_gateway/session_notifications.py b/tui_gateway/session_notifications.py index fb004796bb..5b72d45120 100644 --- a/tui_gateway/session_notifications.py +++ b/tui_gateway/session_notifications.py @@ -4,7 +4,6 @@ desktop UI wiring, HUD surface note. Bodies are rebound onto server.py's globals from __future__ import annotations - import contextlib from .method_ctx import bind_module @@ -369,7 +368,6 @@ def _notif_poll_kanban(sid: str, session: dict) -> None: def _notif_dispatch_event(sid: str, session: dict, evt: dict, text: str) -> None: """Run the claimed (running=True) agent turn for one notification event.""" from tools.async_delegation import claim_event_delivery, complete_event_delivery, release_event_delivery - claim = claim_event_delivery(evt, "tui-poller") if claim is None: return @@ -440,7 +438,6 @@ def _notification_poller_loop(stop_event: threading.Event, sid: str, session: di this session (see _notif_handle_event for ownership routing) and polls ``kanban_notify_subs`` every ``_KANBAN_POLL_SECONDS`` — the delivery path for platform="tui" rows.""" from tools.process_registry import process_registry, format_process_notification - queue = process_registry.completion_queue emitted: set = set() # dedup re-queued events so one completion isn't emitted 50 times while busy handle = lambda evt, deferred: _notif_handle_event( # noqa: E731 @@ -511,7 +508,6 @@ def _wire_desktop_sinks() -> None: return "" with _sessions_lock: return next((sid for sid, s in _sessions.items() if str(s.get("session_key") or "") == session_key), "") - if getattr(process_registry, "on_output", None) is None: process_registry.on_output = lambda session, chunk: _emit( "agent.terminal.output", _owner_sid_for_process(session), {"process_id": session.id, "chunk": chunk} @@ -554,7 +550,6 @@ def _hud_surface_note(session: dict) -> str: if session.get("client_surface") != "hud": return "" from agent.prompt_builder import hud_surface_note - return hud_surface_note(getattr(session.get("agent"), "valid_tool_names", None)) diff --git a/tui_gateway/slash_fuzzy.py b/tui_gateway/slash_fuzzy.py index 01031ef631..c00e864189 100644 --- a/tui_gateway/slash_fuzzy.py +++ b/tui_gateway/slash_fuzzy.py @@ -53,7 +53,6 @@ def fuzzy_rank_slash_items( items: list[dict], catalog: list[dict], query: str ) -> tuple[list[dict], Callable[[dict], float]]: """Merge description/substring matches into ``items`` and sort by score. - ``items`` are the completer's own (prefix-filtered) rows and keep their identity; ``catalog`` is the full command/skill universe, from which any entry the prefix filter missed but the fuzzy scorer matches is appended. Returns the score-sorted rows (stable within a tier) plus a diff --git a/tui_gateway/slash_worker.py b/tui_gateway/slash_worker.py index 4d62cbc6c5..8a8217ba58 100644 --- a/tui_gateway/slash_worker.py +++ b/tui_gateway/slash_worker.py @@ -44,7 +44,6 @@ def _prepare_slash_worker_runtime() -> None: """Start bounded MCP discovery before HermesCLI snapshots tools: each slash_worker child is its own process — the parent ``hermes serve`` discovery thread does not populate this registry.""" from hermes_cli.mcp_startup import start_background_mcp_discovery, wait_for_mcp_discovery - start_background_mcp_discovery(logger=logger, thread_name="slash-worker-mcp-discovery") wait_for_mcp_discovery() @@ -57,7 +56,6 @@ def _start_parent_death_watchdog(original_ppid) -> None: while _in_flight.is_set() and time.monotonic() < deadline: time.sleep(0.05) # let an in-flight command finish/flush os._exit(0) - threading.Thread(target=_loop, daemon=True).start() @@ -81,7 +79,6 @@ def _run(cli: HermesCLI, command: str) -> str: # Desktop chat bubbles render plain text, not ANSI. A command that emits Rich color (e.g. /journey # under the gateway's inherited COLORTERM) would leak raw escapes; strip at this single choke point. from tools.ansi_strip import strip_ansi - return strip_ansi(buf.getvalue().rstrip()) @@ -134,7 +131,6 @@ def main(): # nearby activity). try: from hermes_cli.mem_trim import trim_memory - trim_memory(reason="slash worker command completion") except Exception as exc: # debug, not warning — a persistent failure would repeat every command. diff --git a/tui_gateway/turn_marker.py b/tui_gateway/turn_marker.py index bd58388943..e1b89ac36b 100644 --- a/tui_gateway/turn_marker.py +++ b/tui_gateway/turn_marker.py @@ -89,7 +89,6 @@ def _update(home: Path | str, session_key: str, mutate, what: str) -> None: def record_turn_start(home: Path | str, session_key: str, prompt: str, *, attempts: int = 0) -> None: """Persist the marker for a turn that is about to run. - ``attempts`` counts how many auto-continues led to this run: 0 for a user-initiated turn, N for the Nth automatic re-run — the crash-loop breaker reads it back on the next resume. """