hermes-agent/hermes_cli
kshitij d06104a9ee
fix(dashboard): resolve chat TUI argv off event loop (#48561)
* fix(dashboard): resolve chat TUI argv off event loop

Dashboard chat now resolves its TUI launch command off the
FastAPI/WebSocket event loop. The resolver can run `npm install` /
`npm run build` through `_make_tui_argv()`, and doing that synchronously
in `/api/pty` can block proxy keepalives and other dashboard WebSocket
work long enough for reverse-proxy deployments to drop the chat
connection.

This keeps the current TUI build policy intact: normal production
launches still run the correctness-first `npm run build` path, while
`HERMES_TUI_DIR` remains the prebuilt/no-build path for distros and
containers. The change only moves the potentially slow resolver work to
a worker thread for the dashboard chat path, serialized by an
`asyncio.Lock` so concurrent chat tabs preserve one-build-at-a-time
behavior. `SystemExit` (node/npm missing) and the profile `HTTPException`
path still propagate cleanly through `asyncio.to_thread()`.

Salvaged from #26124 — rebased onto current main. The async wrapper now
threads the `profile` parameter that `_resolve_chat_argv` gained on main
since the PR was opened, so cross-profile chat is preserved.

Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>

* chore: add 0xdany to AUTHOR_MAP

* fix(dashboard): bind chat-argv lock to app.state; cover error propagation

Self-review hardening on top of the salvaged fix:

- Move `_chat_argv_lock` from a module-level `asyncio.Lock()` onto
  `app.state` (initialised in `_lifespan`, lazy fallback via
  `_get_chat_argv_lock`), mirroring `event_lock`. A module-level
  `asyncio.Lock()` binds to whatever event loop is active at import time,
  which is the exact pattern `_get_event_state`'s docstring warns against
  (breaks across TestClient instances / uvicorn reloads). This keeps the
  lock on the running loop.
- Add two tests exercising the real `_resolve_chat_argv_async` →
  `asyncio.to_thread` → lock → re-raise chain: `SystemExit` (node/npm
  missing) and `HTTPException` (invalid profile) both propagate out of the
  worker thread and are caught by `pty_ws`'s existing handlers. The prior
  tests mocked `asyncio.to_thread` away and never covered this path.

* test(dashboard): dedupe pty error-propagation tests; assert close code

simplify-code cleanup pass on the salvage stack:

- Extract the shared scaffolding of the two pty_ws error-propagation tests
  into `_assert_pty_propagates`, keeping the two tests as distinct contracts
  for the `except SystemExit` and `except HTTPException` arms.
- Assert the stable WebSocket close code (1011) instead of relying solely on
  the user-facing "Chat unavailable" notice wording — a behavior contract per
  the AGENTS.md "behavior contracts over snapshots" rule, robust to notice
  rewording. The detail substring ("unknown profile") is still checked for the
  HTTPException case since proving the detail survives the thread hop is the
  point of that test.

No production-code change; the helper exercises the same real
_resolve_chat_argv_async -> asyncio.to_thread -> lock -> re-raise chain.

---------

Co-authored-by: draihan <draihan@student.ubc.ca>
2026-06-18 22:20:52 -04:00
..
dashboard_auth fix(dashboard-auth): warn when public_url override is silently rejected (#43214) 2026-06-10 12:14:57 +10:00
proxy chore: remove dead code — 28 unused functions/classes across 16 files 2026-05-29 04:22:27 -07:00
subcommands feat(skills): find & diff user-modified bundled skills 2026-06-18 12:26:20 +05:30
__init__.py fix(cli): repair non-UTF-8 stdout/stderr on all platforms, not just Windows (#43439) 2026-06-10 02:21:00 -07:00
_parser.py fix(desktop): keep composer usable during reconnect (#45488) 2026-06-13 02:36:09 -07:00
_subprocess_compat.py fix(windows): retry watcher Popen without breakaway when parent job denies it, plus regression tests for the breakaway bit (#40956) 2026-06-07 01:21:58 -07:00
active_sessions.py feat(sessions): add optional max session cap 2026-06-08 15:12:12 -07:00
auth.py feat(billing): /billing terminal billing — interactive TUI + CLI client (#45449) 2026-06-19 01:53:32 +05:30
auth_commands.py fix(auth): add Codex OAuth accounts as distinct pool entries 2026-06-08 11:57:03 -07:00
azure_detect.py feat(azure-foundry): add Microsoft Entra ID auth 2026-05-18 10:14:38 -07:00
backup.py fix(backup): hermes import never overwrites volatile gateway runtime state (NS-501) (#48243) 2026-06-18 15:27:45 +10:00
banner.py fix(mcp): avoid false failed startup status 2026-06-11 09:01:52 -07:00
blueprint_cmd.py refactor(cron): rebrand Cron Recipes -> Automation Blueprints 2026-06-11 10:49:47 -07:00
browser_connect.py feat: auto-launch Chromium-family browser for CDP 2026-05-19 22:34:05 -07:00
build_info.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
bundles.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
callbacks.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
checkpoints.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
claw.py fix: batch of small robustness/correctness fixes from @kyssta-exe 2026-06-01 19:51:03 -07:00
cli_agent_setup_mixin.py fix(cli): gate tool-rendering paths with tool_progress_mode, not quiet_mode 2026-06-08 11:29:53 -07:00
cli_commands_mixin.py feat(delegation): async background subagents via delegate_task(background=true) (#40946) 2026-06-15 13:33:12 -07:00
cli_output.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
clipboard.py fix(clipboard): only read PNG signature bytes, not entire file 2026-05-13 22:54:21 -07:00
codex_models.py fix(codex): drop dead model slugs that HTTP 400 on ChatGPT Pro (#33424) 2026-05-27 12:16:15 -07:00
codex_runtime_plugin_migration.py fix(codex-runtime): de-dup [plugins.X] tables and stop leaking HERMES_HOME into config.toml 2026-05-15 02:31:30 -07:00
codex_runtime_switch.py chore: ruff auto-fix PLR6201 resweep — tuple → set in membership tests (#27355) 2026-05-17 02:29:41 -07:00
colors.py
commands.py feat(billing): /billing terminal billing — interactive TUI + CLI client (#45449) 2026-06-19 01:53:32 +05:30
completion.py fix: batch of small robustness/correctness fixes from @kyssta-exe 2026-06-01 19:51:03 -07:00
config.py feat(prompt): configurable per-platform system-prompt hint overrides 2026-06-18 14:28:01 -07:00
container_boot.py fix(docker): skip gateway reconciliation in dashboard container (autodetect) (#46293) 2026-06-15 20:51:48 +10:00
copilot_auth.py chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) 2026-05-11 11:13:25 -07:00
cron.py revert(cron): remove per-job profile support (PR #28124) (#43956) 2026-06-10 20:46:17 -07:00
curator.py feat(curator): make skill consolidation opt-in (prune stays default-on) (#47840) 2026-06-17 05:20:32 -07:00
curses_ui.py feat(cli): ranked fuzzy search in the curses model picker 2026-06-01 16:58:58 -07:00
dashboard_register.py fix(cli): persist custom --portal-url to .env on dashboard register (#42435) 2026-06-09 13:56:33 +10:00
debug.py feat(dashboard): add Debug Share to the System page (#38600) 2026-06-03 19:37:04 -07:00
default_soul.py
dep_ensure.py feat(dep_ensure): complete Windows bootstrap — dep_ensure + install.ps1 + detection (#27845) 2026-05-18 16:34:24 +05:30
dingtalk_auth.py chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) 2026-05-11 11:13:25 -07:00
doctor.py fix(doctor): recognize nvidia as vendor-slug-accepting provider 2026-06-15 12:24:46 -07:00
dump.py fix(dump): show commit date instead of release date in hermes debug (#48104) 2026-06-17 16:53:42 -07:00
env_loader.py fix(secrets): only apply external secrets once per HERMES_HOME per process (#32271) 2026-05-25 15:18:55 -07:00
fallback_cmd.py fix(fallback): merge fallback_providers with legacy fallback_model configurations 2026-05-23 05:24:57 -07:00
fallback_config.py fix(fallback): merge fallback_providers with legacy fallback_model configurations 2026-05-23 05:24:57 -07:00
gateway.py fix(gateway): defer macOS launchd reload when run inside the gateway tree (#47842) 2026-06-17 05:19:21 -07:00
gateway_enroll.py feat(relay): connector⇄gateway channel auth + signed-HTTP inbound receiver + enroll CLI (#48147) 2026-06-18 12:01:54 +10:00
gateway_windows.py fix(gateway,windows): reliability — JOB breakaway + status --deep probes + test-leak fix (#40909) 2026-06-06 19:53:58 -07:00
goals.py feat(kanban): goal_mode cards run workers in a /goal loop (#35710) 2026-05-31 01:16:33 -07:00
gui_uninstall.py feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI) (#40355) 2026-06-06 18:22:38 -07:00
hooks.py chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) 2026-05-11 11:13:25 -07:00
inventory.py fix(picker): remove max_models=50 cap in interactive model pickers 2026-06-18 13:47:31 -07:00
kanban.py fix(kanban): isolate board override per concurrent call 2026-06-04 07:39:53 -07:00
kanban_db.py fix(kanban): pin assigned profile toolsets for workers (#45590) 2026-06-13 05:50:09 -07:00
kanban_decompose.py docs(kanban): clarify decomposer profile roles 2026-06-06 19:29:00 -07:00
kanban_diagnostics.py chore: remove dead code — 28 unused functions/classes across 16 files 2026-05-29 04:22:27 -07:00
kanban_specify.py fix: guard int(os.getenv()) casts against malformed env vars (#40598) 2026-06-07 06:14:24 -07:00
kanban_swarm.py fix(kanban): CLI dispatch honors max_in_progress/max_spawn from config; swap missing 'avoid-ai-writing' skill for bundled humanizer (#33488, #29415) (#34337) 2026-05-28 21:00:46 -07:00
logs.py feat(debug): include desktop.log in hermes debug share / /debug / hermes logs (#38203) 2026-06-03 05:41:35 -07:00
main.py fix(skills): rmtree scope guard + default pre_update_backup to true (#48200) 2026-06-18 08:53:35 -07:00
managed_uv.py fix(update/windows): don't return _UvResult on Windows (subprocess argv crash) (#39820) 2026-06-05 07:54:08 -05:00
mcp_catalog.py fix(mcp): block exfil-shaped stdio server configs (#46083) 2026-06-14 04:24:14 -07:00
mcp_config.py fix(mcp): block suspicious stdio configs before probe (#46112) 2026-06-14 04:46:54 -07:00
mcp_picker.py feat(mcp): Nous-approved MCP catalog with interactive picker (#30870) 2026-05-26 12:48:14 -07:00
mcp_security.py fix(mcp): block exfil-shaped stdio server configs (#46083) 2026-06-14 04:24:14 -07:00
mcp_startup.py perf(cli): stop eager MCP discovery from blocking agent-capable startup 2026-05-30 07:45:26 -07:00
memory_providers.py fix(desktop): show Hindsight memory provider (#37546) 2026-06-18 16:48:47 -05:00
memory_setup.py feat(memory): improve OpenViking setup UX 2026-06-17 01:04:26 +08:00
middleware.py fix(middleware): single-use next_call guard + deepcopy-safe request copies 2026-06-06 23:07:25 +05:30
migrate.py feat(cli): hermes migrate xai [--apply] [--no-backup] 2026-05-20 09:18:23 -07:00
model_catalog.py feat(models): seed model-catalog disk cache from checkout on update (#42614) 2026-06-08 22:31:06 -07:00
model_cost_guard.py fix(model): require confirmation for expensive model selections 2026-06-10 00:24:06 -07:00
model_normalize.py fix(gemini): strip native self prefixes before generateContent (#36141) 2026-06-13 13:47:08 -07:00
model_setup_flows.py feat(xai): default to grok-build-0.1 2026-06-16 11:50:17 -07:00
model_switch.py fix(picker): keep max_models=0 distinct from unlimited; lock cap semantics 2026-06-18 13:47:31 -07:00
models.py feat(xai): add grok-composer-2.5-fast to xAI OAuth model picker 2026-06-17 09:49:46 -07:00
nous_account.py feat(billing): /credits command — balance + portal top-up handoff (#44776) 2026-06-12 08:51:10 +00:00
nous_billing.py feat(billing): /billing terminal billing — interactive TUI + CLI client (#45449) 2026-06-19 01:53:32 +05:30
nous_subscription.py fix(whatsapp-cloud): review follow-ups for #43921 2026-06-11 07:51:01 -07:00
oneshot.py fix(cli): surface oneshot agent exceptions to stderr with rc=1 2026-05-30 07:31:48 -07:00
pairing.py fix(pairing): enforce lockout on approve_code, not just generate_code (#10195) (#21325) 2026-05-07 07:18:21 -07:00
partial_compress.py Inspired by Claude Code: /compress here [N] — boundary-aware 'summarize up to here' (#35048) 2026-05-29 17:49:15 -07:00
platforms.py feat(whatsapp): add WhatsApp Business Cloud API adapter 2026-05-23 01:07:01 -04:00
plugins.py fix(desktop): keep composer usable during reconnect (#45488) 2026-06-13 02:36:09 -07:00
plugins_cmd.py fix(plugins): normalize browser-pasted GitHub repo URLs (#33539) 2026-06-13 13:23:59 -07:00
portal_cli.py feat(cli): make hermes portal run the full quick-setup Nous flow (model picker) 2026-06-04 02:20:31 +05:30
profile_describer.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
profile_distribution.py fix(dist): stop USER_OWNED_EXCLUDE from filtering nested directories 2026-06-07 21:50:57 -07:00
profiles.py fix(s6): register profile gateways without auto-starting (#46266) 2026-06-15 11:43:23 +10:00
prompt_size.py feat(cli): add hermes prompt-size diagnostic (#35276) 2026-05-30 02:53:42 -07:00
providers.py fix(doctor): allow vendor slugs for named custom providers 2026-06-08 15:53:09 -07:00
psutil_android.py fix(android): reject unsafe tar members in psutil compatibility installer 2026-05-28 02:36:09 -07:00
pt_input_extras.py fix(cli): ignore terminal focus reports (salvage of #16780) 2026-05-29 00:31:44 -07:00
pty_bridge.py fix(pty-bridge): mark os.killpg/getpgid windows-footgun-ok (POSIX-only module) 2026-06-08 07:03:12 -07:00
relaunch.py fix(windows): prefer npm.cmd over npm.ps1, skip .py argv0 in relaunch 2026-05-08 14:27:40 -07:00
runtime_provider.py fix(desktop): never persist or restore a named custom provider as bare "custom" (#48547) 2026-06-18 11:11:51 -07:00
secret_prompt.py feat(memory): improve OpenViking setup UX 2026-06-17 01:04:26 +08:00
secrets_cli.py fix(secrets): fail early with clear error when bitwarden setup runs without TTY (#40571) 2026-06-06 18:36:40 -07:00
security_advisories.py fix(stt,tts): restore mistralai — 2.4.8 is clean, ban lifted (#34841) 2026-05-29 13:24:12 -07:00
security_audit.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
send_cmd.py fix(send): helpful error when --file gets a binary; document MEDIA: attachments (#45116) 2026-06-12 11:48:06 -07:00
service_manager.py fix(docker): supervised gateway uses --replace to take over stale holder (NS-505) (#47555) 2026-06-18 10:49:02 +10:00
session_listing.py fix: harden salvaged session and browser improvements 2026-06-15 07:46:34 -07:00
session_recap.py chore: ruff auto-fix PLR6201 resweep — tuple → set in membership tests (#27355) 2026-05-17 02:29:41 -07:00
setup.py feat(zai): add GLM-5.2 with verified 1M context window 2026-06-14 13:50:36 -07:00
setup_whatsapp_cloud.py fix(whatsapp-cloud): review follow-ups for #43921 2026-06-11 07:51:01 -07:00
skills_config.py fix(skills): apply global|platform disabled union to all resolution sites 2026-06-14 22:54:54 +05:30
skills_hub.py feat(skills): find & diff user-modified bundled skills 2026-06-18 12:26:20 +05:30
skin_engine.py fix(tui): improve charizard completion menu contrast 2026-05-18 20:05:23 -07:00
slack_cli.py fix(slack): enable writable app home DMs in manifest 2026-05-08 17:01:12 -07:00
status.py Merge commit '6110aed9b' into feat/whatsapp-cloud-api 2026-06-10 21:39:22 -04:00
stdio.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
suggestions_cmd.py refactor(cron): rebrand Cron Recipes -> Automation Blueprints 2026-06-11 10:49:47 -07:00
telegram_managed_bot.py Add CLI Telegram QR onboarding 2026-06-05 03:20:10 -07:00
timeouts.py perf(agent-loop): cut 47% of per-conversation function calls via 3 targeted hot-path optimizations (#28866) 2026-05-19 14:25:10 -07:00
tips.py fix: make profile subprocess HOME policy explicit 2026-06-14 03:20:21 -07:00
tools_config.py refactor: remove agent-callable send_message tool (#47856) 2026-06-17 07:11:23 -07:00
uninstall.py feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI) (#40355) 2026-06-06 18:22:38 -07:00
voice.py fix(tui): restore voice push-to-talk parity (#20897) 2026-05-06 15:49:59 -07:00
web_server.py fix(dashboard): resolve chat TUI argv off event loop (#48561) 2026-06-18 22:20:52 -04:00
webhook.py fix(state): restrict sensitive store file permissions 2026-05-24 04:55:18 -07:00
win_pty_bridge.py feat(windows): enable dashboard /chat tab via ConPTY (win_pty_bridge) + tests (#42251) 2026-06-08 11:32:43 -07:00
write_approval_commands.py refactor(memory,skills): replace tri-state write_mode with boolean write_approval (default off) (#43354) 2026-06-09 23:21:14 -07:00
xai_retirement.py fix(xai): align migrate retirement map with docs 2026-05-20 09:18:23 -07:00