mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-18 14:52:04 +00:00
6 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e9b95dfd19 |
fix(docker): include apps/shared in dashboard image build
The shared websocket package is a web file: dependency but was excluded by .dockerignore and never copied into the Docker build context. Also fix tsc -b errors: expose buildWsUrl on api and drop the GatewayClient state getter that conflicted with the shared base class. |
||
|
|
dfb561a3ae |
refactor(desktop+dashboard): extract shared WebSocket/JSON-RPC layer
The Electron desktop app and the web dashboard each carried their own copy of the tui_gateway JSON-RPC WebSocket client plus near-identical auth'd WS-URL construction. The dashboard's copy was the historical source of the "is the dashboard required to run the desktop app?" confusion, since the two surfaces looked coupled. Consolidate the genuinely shared transport into the existing framework-agnostic `@hermes/shared` package so both surfaces consume it independently — neither app depends on the other: - Move `resolveGatewayWsUrl` + `GatewayReauthRequiredError` (single-use OAuth ticket re-mint vs long-lived token fallback) into `@hermes/shared`; desktop now imports them directly. - Add `buildHermesWebSocketUrl`, one base-path/scheme/auth-aware URL builder, and route every dashboard WS endpoint through it (`/api/ws`, `/api/events`, `/api/pty`, plugin WS URLs). - Reduce the dashboard `GatewayClient` to a thin subclass of the shared `JsonRpcGatewayClient`, deleting ~210 lines of duplicated pending-call /event-dispatch/connect plumbing while keeping its dashboard-specific ticket-vs-token auth selection. - Drop the stale "start it with --tui" chat banner, which implied the dashboard flag was required. Behavior is preserved on both surfaces; the dashboard additionally inherits the shared client's 15s connect timeout (previously desktop-only), so a hung connect now fails fast instead of pinning the composer in "connecting". |
||
|
|
8971e94831 |
feat(dashboard-auth): SPA WS auth — getWsTicket() + buildWsAuthParam()
Phase 5 task 5.3. The dashboard's three WS-using surfaces (ChatPage, gatewayClient, ChatSidebar) previously hardcoded ?token=<session>. In gated mode the server rejects that path; the SPA must mint a single-use ticket via POST /api/auth/ws-ticket and pass ?ticket= on the upgrade. web/src/lib/api.ts: adds getWsTicket() (POST /api/auth/ws-ticket with credentials: 'include') and buildWsAuthParam() — a helper that returns ['ticket', <minted>] in gated mode and ['token', <session>] in loopback. Window.__HERMES_AUTH_REQUIRED__ is read from the server-injected bootstrap script and toggles the path. Documented as the bridge from cookie auth (REST) to WS auth. web/src/pages/ChatPage.tsx: buildWsUrl() now takes an [authName, authValue] pair instead of a bare token. The WS construct is wrapped in an IIFE so the outer effect can stay synchronous (the cleanup returns the effect's disposer at top level). onDataDisposable + onResizeDisposable hoisted to `let` bindings the cleanup closes over. web/src/lib/gatewayClient.ts: connect() branches on window.__HERMES_AUTH_REQUIRED__ before opening /api/ws. Explicit token overrides win (test-only path); otherwise gated → fetch ticket, loopback → use injected session token. web/src/components/ChatSidebar.tsx: events-feed WS opens through the same IIFE pattern as ChatPage. The ws local is hoisted so the cleanup's ws?.close() works after the async mint resolves. Server side already injects window.__HERMES_AUTH_REQUIRED__ in _serve_index (Phase 3.5). |
||
|
|
74031e1e2a |
fix(dashboard): respect HERMES_BASE_PATH in WebSocket URLs (#25547)
When the dashboard is reverse-proxied under a path prefix (`X-Forwarded-Prefix: /dashboard`), the SPA already routes its `/api/...` REST traffic through `HERMES_BASE_PATH` via `web/src/lib/api.ts`. Three WebSocket URLs constructed elsewhere were still hardcoded to root `/api/...` and so opened `wss://host/api/...` instead of `wss://host/dashboard/api/...`, forcing operators to forward selected root API/WS paths through the reverse proxy as a workaround (see issue #25547). Add `HERMES_BASE_PATH` between `host` and `/api/...` in the three constructed WebSocket URLs: - `web/src/pages/ChatPage.tsx` — PTY WebSocket - `web/src/components/ChatSidebar.tsx` — events subscriber - `web/src/lib/gatewayClient.ts` — JSON-RPC gateway WebSocket When the dashboard is served at root, `HERMES_BASE_PATH === """ and the URLs are bit-for-bit identical to before. Under a prefix, the WebSocket connections now go through the same proxy path the REST calls already use. Note: bundled dashboard plugins (kanban, hermes-achievements) embed `"/api/plugins/..."` in their compiled `dist/index.js` and remain out of scope here — those need source-side fixes per plugin. Fixes #25547. |
||
|
|
7fa70b6c87
|
refactor: /btw is now an alias for /background (#16053)
The ephemeral no-tools side-question variant of /btw confused users who expected 'by-the-way' to mean 'run this off to the side with tools' — they'd type /btw and get a toolless agent that couldn't do the work. /bg worked because it was /background with full tools. Collapse the two: /btw and /bg both alias to /background. One command, one behavior, no more gotchas about which variant has tools. Removed: - _handle_btw_command in cli.py and gateway/run.py - _run_btw_task + _active_btw_tasks state in gateway/run.py - prompt.btw JSON-RPC method + btw.complete event in tui_gateway - BtwStartResponse type + btw.complete case in ui-tui - Standalone /btw slash tree registration in Discord - Standalone btw CommandDef in hermes_cli/commands.py Updated: - background CommandDef aliases: (bg,) -> (bg, btw) - TUI session.ts: local btw handler merged into background - Docs and tips updated to describe /btw as a /background alias |
||
|
|
f49afd3122 |
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.
(cherry picked from commit
|