diff --git a/tests/hermes_cli/test_tui_resume_flow.py b/tests/hermes_cli/test_tui_resume_flow.py index 7d0d0d115..c7e551ea1 100644 --- a/tests/hermes_cli/test_tui_resume_flow.py +++ b/tests/hermes_cli/test_tui_resume_flow.py @@ -17,11 +17,6 @@ def _args(**overrides): @pytest.fixture def main_mod(monkeypatch): - """cmd_chat entry with the first-run provider-gate stubbed past. - - `cmd_chat` now early-exits when no API key is configured (post-merge); - these tests exercise the post-config routing so we fake the check out. - """ import hermes_cli.main as mod monkeypatch.setattr(mod, "_has_any_provider_configured", lambda: True) diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 18631d0ee..9c6305ded 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -231,8 +231,6 @@ def test_config_set_personality_resets_history_and_returns_info(monkeypatch): monkeypatch.setattr(server, "_session_info", lambda agent: {"model": getattr(agent, "model", "?")}) monkeypatch.setattr(server, "_restart_slash_worker", lambda session: None) monkeypatch.setattr(server, "_emit", lambda *args: emits.append(args)) - # _write_config_key writes to ~/.hermes/config.yaml — races with other - # xdist workers that touch the same file. Stub it out. monkeypatch.setattr(server, "_write_config_key", lambda path, value: None) resp = server.handle_request( diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 0af85fd2e..262f9abb0 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -201,12 +201,6 @@ def handle_request(req: dict) -> dict | None: def _wait_agent(session: dict, rid: str, timeout: float = 30.0) -> dict | None: - """Block until the session's agent has been built. - - Returns a JSON-RPC error dict on failure/timeout, ``None`` when the - agent is live. Cheap no-op when ``agent_ready`` is absent (sessions - from `session.resume`, which builds inline) or already set. - """ ready = session.get("agent_ready") if ready is not None and not ready.wait(timeout=timeout): return _err(rid, 5032, "agent initialization timed out") @@ -215,21 +209,11 @@ def _wait_agent(session: dict, rid: str, timeout: float = 30.0) -> dict | None: def _sess_nowait(params, rid): - """Resolve session without gating on agent readiness — for handlers - that only touch placeholder fields (cols, attached_images) and - shouldn't eat the agent-build window on cold start.""" s = _sessions.get(params.get("session_id") or "") return (s, None) if s else (None, _err(rid, 4001, "session not found")) def _sess(params, rid): - """Resolve session from params + block on ``_wait_agent``. - - `session.create` builds the agent on a background thread (~500–1500ms - cold) so the placeholder session exists before ``session["agent"]`` - is populated. Routing every agent-touching RPC through `_sess` hides - that window — reads become free once the agent is live. - """ s, err = _sess_nowait(params, rid) return (None, err) if err else (s, _wait_agent(s, rid)) @@ -1062,13 +1046,6 @@ def _history_to_messages(history: list[dict]) -> list[dict]: @method("session.create") def _(rid, params: dict) -> dict: - """Non-blocking session creation. - - Returns the sid + minimal info right away; the heavy agent init runs - on a background thread and broadcasts `session.info` when tools and - skills are wired. Handlers that touch ``session["agent"]`` go through - `_sess`, which gates on the `agent_ready` event. - """ sid = uuid.uuid4().hex[:8] key = _new_session_key() cols = int(params.get("cols", 80)) @@ -1076,8 +1053,6 @@ def _(rid, params: dict) -> dict: ready = threading.Event() - # Placeholder session so subsequent RPCs find the sid and can wait on - # `agent_ready`; anything derived from the agent is filled in by `_build`. _sessions[sid] = { "agent": None, "agent_error": None, @@ -1112,7 +1087,7 @@ def _(rid, params: dict) -> dict: try: session["slash_worker"] = _SlashWorker(key, getattr(agent, "model", _resolve_model())) except Exception: - pass # slash.exec will surface the real failure + pass try: from tools.approval import register_gateway_notify, load_permanent_allowlist @@ -1544,8 +1519,6 @@ def _(rid, params: dict) -> dict: @method("input.detect_drop") def _(rid, params: dict) -> dict: - # Pure text pattern-matching — bypass the agent-ready gate so the first - # post-startup send doesn't stack the wait on top of `prompt.submit`'s. session, err = _sess_nowait(params, rid) if err: return err diff --git a/ui-tui/src/app/createGatewayEventHandler.ts b/ui-tui/src/app/createGatewayEventHandler.ts index 105a90707..02057e807 100644 --- a/ui-tui/src/app/createGatewayEventHandler.ts +++ b/ui-tui/src/app/createGatewayEventHandler.ts @@ -167,13 +167,10 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: patchUiState(state => ({ ...state, info, - // agent just came online → flip the 'starting agent…' placeholder. - // leave running/interrupted/error statuses alone. status: state.status === 'starting agent…' ? 'ready' : state.status, usage: info.usage ? { ...state.usage, ...info.usage } : state.usage })) - // upgrade the seeded/partial intro row in-place with the real info setHistoryItems(prev => prev.map(m => (m.kind === 'intro' ? { ...m, info } : m))) return diff --git a/ui-tui/src/app/useMainApp.ts b/ui-tui/src/app/useMainApp.ts index 1c69c78a0..8f4509594 100644 --- a/ui-tui/src/app/useMainApp.ts +++ b/ui-tui/src/app/useMainApp.ts @@ -93,9 +93,6 @@ export function useMainApp(gw: GatewayClient) { } }, [stdout]) - // Seed with an info-less intro so the banner paints on the first frame, - // before gateway.ready / session.create resolve. Replaced by - // `introMsg(info)` as soon as session.info arrives. const [historyItems, setHistoryItems] = useState(() => [{ kind: 'intro', role: 'system', text: '' }]) const [lastUserMsg, setLastUserMsg] = useState('') const [stickyPrompt, setStickyPrompt] = useState('') @@ -130,9 +127,6 @@ export function useMainApp(gw: GatewayClient) { const hasSelection = useHasSelection() const selection = useSelection() - // Bind a uniform selection bg so drag-to-select shows one solid color - // across the whole range instead of SGR-inverse (which swaps each cell's - // fg → bg and fragments over amber/gold/dim text). Re-fires on skin swap. useEffect(() => { selection.setSelectionBgColor(ui.theme.color.selectionBg) }, [selection, ui.theme.color.selectionBg]) @@ -379,8 +373,6 @@ export function useMainApp(gw: GatewayClient) { sys }) - // Flush any pre-session queued input the moment the session lands. - // `message.complete` drains the rest; this just kicks off the first send. const prevSidRef = useRef(null) useEffect(() => { const prev = prevSidRef.current diff --git a/ui-tui/src/app/useSessionLifecycle.ts b/ui-tui/src/app/useSessionLifecycle.ts index 4a496f5f7..3319f7a07 100644 --- a/ui-tui/src/app/useSessionLifecycle.ts +++ b/ui-tui/src/app/useSessionLifecycle.ts @@ -107,8 +107,6 @@ export function useSessionLifecycle(opts: UseSessionLifecycleOptions) { resetSession() setSessionStartedAt(Date.now()) - // session.create returns instantly with partial info (no `version`); - // the `session.info` event flips status to 'ready' once the agent is live. patchUiState({ info, sid: r.session_id, diff --git a/ui-tui/src/bootBanner.ts b/ui-tui/src/bootBanner.ts index 35fd3ce40..3cbd0a089 100644 --- a/ui-tui/src/bootBanner.ts +++ b/ui-tui/src/bootBanner.ts @@ -1,8 +1,3 @@ -// Raw-ANSI banner painted to stdout before React/Ink load, giving the user -// instant visual feedback during the ~170ms dynamic-import window. -// `` wipes the normal-screen buffer when Ink mounts, so -// there's no double-banner and palette drift vs. DEFAULT_THEME is harmless. - const GOLD = '\x1b[38;2;255;215;0m' const AMBER = '\x1b[38;2;255;191;0m' const BRONZE = '\x1b[38;2;205;127;50m' diff --git a/ui-tui/src/entry.tsx b/ui-tui/src/entry.tsx index 3c21de93e..e0a437934 100644 --- a/ui-tui/src/entry.tsx +++ b/ui-tui/src/entry.tsx @@ -1,9 +1,5 @@ #!/usr/bin/env node -// Import order matters for cold start: `GatewayClient` + `bootBanner` have -// only node-builtin deps (<20ms), so we can paint the banner and spawn the -// python gateway before loading @hermes/ink + App (~170ms combined). -// `` wipes the normal-screen buffer on Ink mount, so the -// boot banner is replaced seamlessly by the real React render. +// Order matters: paint banner + spawn python before loading @hermes/ink. import { bootBanner } from './bootBanner.js' import { GatewayClient } from './gatewayClient.js' diff --git a/ui-tui/src/theme.ts b/ui-tui/src/theme.ts index cf85786c7..ddd722e53 100644 --- a/ui-tui/src/theme.ts +++ b/ui-tui/src/theme.ts @@ -94,8 +94,6 @@ export const DEFAULT_THEME: Theme = { statusWarn: '#FFD700', statusBad: '#FF8C00', statusCritical: '#FF6B6B', - // muted navy — sits under gold/amber fg without fighting it, swaps - // cleanly with SGR-inverse that fragmented per fg color selectionBg: '#3a3a55', diffAdded: 'rgb(220,255,220)',