hermes-agent/tests/hermes_cli
emozilla 29b337bca7 feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard
Exposes hermes --tui over a PTY-backed WebSocket so the dashboard can
embed the real TUI rather than reimplement its surface. The browser
attaches xterm.js to the socket; keystrokes flow in, PTY output bytes
flow out.

Architecture:

    browser <Terminal> (xterm.js)
           │  onData ───► ws.send(keystrokes)
           │  onResize ► ws.send('\x1b[RESIZE:cols;rows]')
           │  write   ◄── ws.onmessage (PTY bytes)
           ▼
    FastAPI /api/pty (token-gated, loopback-only)
           ▼
    PtyBridge (ptyprocess) ── spawns node ui-tui/dist/entry.js ──► tui_gateway + AIAgent

Components
----------

hermes_cli/pty_bridge.py
  Thin wrapper around ptyprocess.PtyProcess: byte-safe read/write on the
  master fd via os.read/os.write (not PtyProcessUnicode — ANSI is
  inherently byte-oriented and UTF-8 boundaries may land mid-read),
  non-blocking select-based reads, TIOCSWINSZ resize, idempotent
  SIGHUP→SIGTERM→SIGKILL teardown, platform guard (POSIX-only; Windows
  is WSL-supported only).

hermes_cli/web_server.py
  @app.websocket("/api/pty") endpoint gated by the existing
  _SESSION_TOKEN (via ?token= query param since browsers can't set
  Authorization on WS upgrades). Loopback-only enforcement. Reader task
  uses run_in_executor to pump PTY bytes without blocking the event
  loop. Writer loop intercepts a custom \x1b[RESIZE:cols;rows] escape
  before forwarding to the PTY. The endpoint resolves the TUI argv
  through a _resolve_chat_argv hook so tests can inject fake commands
  without building the real TUI.

Tests
-----

tests/hermes_cli/test_pty_bridge.py — 12 unit tests: spawn, stdout,
stdin round-trip, EOF, resize (via TIOCSWINSZ + tput readback), close
idempotency, cwd, env forwarding, unavailable-platform error.

tests/hermes_cli/test_web_server.py — TestPtyWebSocket adds 7 tests:
missing/bad token rejection (close code 4401), stdout streaming,
stdin round-trip, resize escape forwarding, unavailable-platform ANSI
error frame + 1011 close, resume parameter forwarding to argv.

96 tests pass under scripts/run_tests.sh.
2026-04-21 02:48:16 -04:00
..
__init__.py
…
test_ai_gateway_models.py refactor(ai-gateway): single source of truth for model catalog (#13304) 2026-04-20 22:21:21 -07:00
test_anthropic_oauth_flow.py
…
test_anthropic_provider_persistence.py
…
test_api_key_providers.py test: stop testing mutable data — convert change-detectors to invariants (#13363) 2026-04-20 23:20:33 -07:00
test_arcee_provider.py test: stop testing mutable data — convert change-detectors to invariants (#13363) 2026-04-20 23:20:33 -07:00
test_argparse_flag_propagation.py feat: shell hooks — wire shell scripts as Hermes hook callbacks 2026-04-20 20:53:51 -07:00
test_atomic_json_write.py
…
test_atomic_yaml_write.py
…
test_auth_codex_provider.py
…
test_auth_commands.py
…
test_auth_nous_provider.py fix(auth): use ssl.SSLContext for CA bundle instead of deprecated string path (#12706) 2026-04-19 22:44:35 -07:00
test_auth_provider_gate.py
…
test_auth_qwen_provider.py
…
test_aux_config.py fix(aux): add session_search extra_body and concurrency controls 2026-04-20 00:47:39 -07:00
test_backup.py fix(backup): handle files with pre-1980 timestamps 2026-04-20 00:47:40 -07:00
test_banner.py
…
test_banner_git_state.py
…
test_banner_skills.py
…
test_chat_skills_flag.py
…
test_claw.py
…
test_clear_stale_base_url.py
…
test_cmd_update.py
…
test_coalesce_session_args.py
…
test_codex_cli_model_picker.py fix(tests): unstick CI — sweep stale tests from recent merges (#12670) 2026-04-19 12:39:58 -07:00
test_codex_models.py fix: remove codex spark model support 2026-04-20 04:51:44 -07:00
test_commands.py fix: enable plugins in config.yaml for lazy-discovery tests 2026-04-20 05:11:39 -07:00
test_completion.py
…
test_config.py test: stop testing mutable data — convert change-detectors to invariants (#13363) 2026-04-20 23:20:33 -07:00
test_config_env_expansion.py
…
test_config_env_refs.py
…
test_config_validation.py fix(gemini): tighten native routing and streaming replay 2026-04-19 12:40:08 -07:00
test_container_aware_cli.py
…
test_copilot_auth.py
…
test_cron.py feat(skills): consolidate find-nearby into maps as a single location skill 2026-04-19 05:19:22 -07:00
test_custom_provider_model_switch.py
…
test_debug.py
…
test_deprecated_cwd_warning.py
…
test_detect_api_mode_for_url.py fix: restrict provider URL detection to exact hostname matches 2026-04-20 22:14:29 -07:00
test_determine_api_mode_hostname.py fix: extend hostname-match provider detection across remaining call sites 2026-04-20 22:14:29 -07:00
test_dingtalk_auth.py
…
test_doctor.py
…
test_doctor_command_install.py
…
test_env_loader.py
…
test_env_sanitize_on_load.py
…
test_gateway.py
…
test_gateway_linger.py
…
test_gateway_runtime_health.py
…
test_gateway_service.py
…
test_gateway_wsl.py
…
test_gemini_provider.py test: stop testing mutable data — convert change-detectors to invariants (#13363) 2026-04-20 23:20:33 -07:00
test_hooks_cli.py feat: shell hooks — wire shell scripts as Hermes hook callbacks 2026-04-20 20:53:51 -07:00
test_launcher.py
…
test_logs.py
…
test_managed_installs.py
…
test_mcp_config.py
…
test_mcp_tools_config.py
…
test_memory_reset.py
…
test_model_normalize.py
…
test_model_picker_viewport.py
…
test_model_provider_persistence.py
…
test_model_switch_copilot_api_mode.py
…
test_model_switch_custom_providers.py fix(model_switch): enumerate dict-format models in /model picker 2026-04-19 11:07:29 -07:00
test_model_switch_opencode_anthropic.py feat(providers): extend request_timeout_seconds to all client paths 2026-04-19 11:23:00 -07:00
test_model_switch_variant_tags.py
…
test_model_validation.py fix(cli): send User-Agent in /v1/models probe to pass Cloudflare 1010 2026-04-20 04:56:30 -07:00
test_models.py
…
test_non_ascii_credential.py fix(env_loader): warn when non-ASCII stripped from credential env vars (#13300) 2026-04-20 22:14:03 -07:00
test_nous_hermes_non_agentic.py
…
test_nous_subscription.py
…
test_ollama_cloud_auth.py
…
test_ollama_cloud_provider.py
…
test_opencode_go_in_model_list.py
…
test_overlay_slug_resolution.py
…
test_path_completion.py
…
test_placeholder_usage.py
…
test_plugin_cli_registration.py
…
test_plugins.py fix: enable plugins in config.yaml for lazy-discovery tests 2026-04-20 05:11:39 -07:00
test_plugins_cmd.py
…
test_profile_export_credentials.py
…
test_profiles.py
…
test_provider_config_validation.py fix(config): validate providers config entries — reject non-URL base, accept camelCase aliases (#9332) 2026-04-20 04:52:50 -07:00
test_pty_bridge.py feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard 2026-04-21 02:48:16 -04:00
test_reasoning_effort_menu.py
…
test_runtime_provider_resolution.py
…
test_session_browse.py
…
test_sessions_delete.py
…
test_set_config_value.py
…
test_setup.py
…
test_setup_agent_settings.py
…
test_setup_hermes_script.py
…
test_setup_matrix_e2ee.py
…
test_setup_model_provider.py
…
test_setup_noninteractive.py
…
test_setup_openclaw_migration.py Update env vars for openclaw migration 2026-04-20 14:56:04 -07:00
test_setup_prompt_menus.py
…
test_skills_config.py
…
test_skills_hub.py
…
test_skills_install_flags.py
…
test_skills_skip_confirm.py
…
test_skills_subparser.py
…
test_skin_engine.py
…
test_status.py
…
test_status_model_provider.py
…
test_subparser_routing_fallback.py
…
test_subprocess_timeouts.py
…
test_terminal_menu_fallbacks.py
…
test_timeouts.py fix(config): add stale timeout settings 2026-04-20 00:52:50 -07:00
test_tips.py
…
test_tool_token_estimation.py
…
test_tools_config.py fix(tools): keep default-off toolsets disabled 2026-04-20 20:52:50 -07:00
test_tools_disable_enable.py
…
test_tui_npm_install.py
…
test_tui_resume_flow.py
…
test_update_autostash.py
…
test_update_check.py test: remove 8 flaky tests that fail under parallel xdist scheduling (#12784) 2026-04-19 19:38:02 -07:00
test_update_config_clears_custom_fields.py fix(anthropic): complete third-party Anthropic-compatible provider support (#12846) 2026-04-19 22:43:09 -07:00
test_update_gateway_restart.py
…
test_update_hangup_protection.py
…
test_user_providers_model_switch.py fix(model-picker): dedup overlapping providers: dict and custom_providers: list entries 2026-04-19 22:15:49 -07:00
test_web_server.py feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard 2026-04-21 02:48:16 -04:00
test_webhook_cli.py
…
test_xiaomi_provider.py test: stop testing mutable data — convert change-detectors to invariants (#13363) 2026-04-20 23:20:33 -07:00