Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
188 commits
Select commit Hold shift + click to select a range
4526fcc
fix(desktop): make project "Add folder" picker remote-gateway aware
OutThisLife Jun 28, 2026
304f065
style(desktop): tighten pickProjectFolder comment
OutThisLife Jun 28, 2026
fc86e35
feat(desktop): make the git cockpit work over a remote gateway
OutThisLife Jun 28, 2026
e4cf3a2
refactor(web_git): unify porcelain-v2 parsing into one walker
OutThisLife Jun 28, 2026
9b71221
fix(desktop): write project IDEA.md through the remote-aware fs path
OutThisLife Jun 28, 2026
4e9439c
fix(desktop): route composer context picking through remote-aware fs
OutThisLife Jun 28, 2026
453f134
refactor(desktop): centralize remote git REST routing
OutThisLife Jun 28, 2026
8d8c711
refactor(desktop): keep remote fs routing inside the fs facade
OutThisLife Jun 28, 2026
19bae1b
test(desktop): assert new backend sessions carry workspace cwd
OutThisLife Jun 28, 2026
b31b0b9
docs: reconcile docs with code across last 3 releases (#54254)
teknium1 Jun 28, 2026
9a0010f
fix(windows): cover remaining console-flash spawn legs (#54417)
teknium1 Jun 28, 2026
c754235
fix(desktop): remote project picker UX and profile-scoped fs/git routing
OutThisLife Jun 28, 2026
6121009
fix(browser): extend private-network guard to browser_get_images
srojk34 Jun 28, 2026
c648ecd
fix(telegram): reject unauthorized users before event construction (#…
teknium1 Jun 28, 2026
f9b469d
test(web_git): assert default branch invariant, not hardcoded main
OutThisLife Jun 28, 2026
e5d22ab
fix(daytona): quote single-upload mkdir parent path (#54440)
teknium1 Jun 28, 2026
28097d9
Merge pull request #54385 from NousResearch/bb/project-folder-picker-…
OutThisLife Jun 28, 2026
95994bb
fix(windows): repair missing hermes.exe after pip install (#52931)
HexLab98 Jun 26, 2026
76bb8f4
test(cli): cover Windows console script repair (#52931)
HexLab98 Jun 26, 2026
df8e252
fix(windows): verify launchers after primary install
helix4u Jun 28, 2026
4c2961c
fix(curator): never archive cron-referenced skills + floor use=0 prun…
teknium1 Jun 28, 2026
86e6490
fix(gateway): preserve sessions across restarts (#54442)
teknium1 Jun 28, 2026
b0b7ff0
fix(provider): auto+base_url bypasses cloud API when custom endpoint …
Mibayy Jun 28, 2026
95f2919
perf(startup): lazy-load gateway platform adapters (#54448)
teknium1 Jun 28, 2026
e7d4ade
fix(anthropic): ignore stale non-Anthropic base_url across all resolu…
clovericbot Jun 28, 2026
c8b8696
docs: add PR infographic for anthropic stale base_url guard
teknium1 Jun 28, 2026
16ff1a3
Merge pull request #54457 from NousResearch/bb/windows-console-launch…
OutThisLife Jun 28, 2026
d65468e
fix(security): SSRF guard yuanbao media download_url (#54470)
teknium1 Jun 28, 2026
980622d
perf(startup): parse config + plugin manifests with libyaml CSafeLoad…
teknium1 Jun 28, 2026
32087e4
fix(windows): hide console flash on checkpoint git + skills_hub gh pr…
OutThisLife Jun 28, 2026
ee22d85
fix(windows): hide pdftoppm console flash on PDF attach
OutThisLife Jun 28, 2026
cb1bb1a
refactor(windows): unify windowless spawn form across the touched sites
OutThisLife Jun 28, 2026
d0d2cf1
Merge pull request #54492 from NousResearch/bb/windows-hide-checkpoin…
OutThisLife Jun 28, 2026
27f0324
fix(dashboard): stop ElevenLabs voice-list 401 log spam
OutThisLife Jun 27, 2026
f34cf7e
test(gmi): stub profile fetch_models in static-fallback test
OutThisLife Jun 28, 2026
65d45a0
Merge pull request #53386 from NousResearch/bb/elevenlabs-voices-401-…
OutThisLife Jun 28, 2026
cd5fb76
fix(desktop): restore cross-wired runtime-id guard on session resume
OutThisLife Jun 28, 2026
10043c6
Merge pull request #54503 from NousResearch/bb/fix-desktop-cross-wire…
OutThisLife Jun 28, 2026
6875d6c
feat(desktop): multi-terminal panel with side tab rail
OutThisLife Jun 29, 2026
c1bb34d
fix(desktop): keep inactive terminals sized so switching doesn't garble
OutThisLife Jun 29, 2026
2d55ff8
feat(desktop): ⌘W closes the focused terminal
OutThisLife Jun 29, 2026
b02f453
refactor(desktop): generalize focus check to isFocusWithin primitive
OutThisLife Jun 29, 2026
6e12f8c
fix(desktop): force a repaint when a terminal is re-activated
OutThisLife Jun 29, 2026
ad831dd
feat(desktop): mirror agent background terminals as read-only tabs
OutThisLife Jun 29, 2026
520212c
feat(desktop): stream agent terminal output live instead of polling
OutThisLife Jun 29, 2026
6ac9ba9
fix(desktop): seed agent terminal tabs from process snapshots
OutThisLife Jun 29, 2026
5d661a3
fix(desktop): show the agent command before terminal output arrives
OutThisLife Jun 29, 2026
7cfa2fa
fix(docker): gate resource limit flags on cgroup controller availabil…
benbarclay Jun 29, 2026
9860d93
fix(terminal): require approval for host-bound Docker commands (#54483)
teknium1 Jun 29, 2026
3483424
fix(security): redact bare-token credentials in URL userinfo (#6396) …
teknium1 Jun 29, 2026
f1cbe43
fix(gateway): log error-notification failures instead of silently swa…
teknium1 Jun 29, 2026
5c1ac6c
fix(config): strip `export ` prefix in .env parsers across three modules
aaronlab Apr 9, 2026
490f215
test: cover export-prefix stripping in .env parsers (PR #6659)
teknium1 Jun 28, 2026
ec148f5
fix(agent): guard Anthropic interrupt, cap vision data-URL size
aaronlab Jun 28, 2026
27ddd8f
fix(gateway): sanitize agent error messages, validate webhook gh args
aaronlab Jun 28, 2026
11183e8
fix(profiles): validate custom alias names to prevent path traversal
teknium1 Jun 28, 2026
9912207
feat(desktop): unify non-settings overlays under a shared Panel primi…
OutThisLife Jun 29, 2026
317b948
chore(desktop): drop dead overlay primitives
OutThisLife Jun 29, 2026
dda3268
fix(approvals): warn and default to manual on unknown approvals.mode
LIC99 Jun 28, 2026
c8fd47b
docs: add PR infographic for approval mode validation
teknium1 Jun 28, 2026
9f02eea
style(desktop): prettier + eslint pass
OutThisLife Jun 29, 2026
e117cfd
feat(desktop): live agent terminals + agent-driven tab close
OutThisLife Jun 29, 2026
dee41d0
feat(dashboard): catalogue all memory-provider API keys in OPTIONAL_E…
benbarclay Jun 29, 2026
adacb16
fix(desktop): make agent terminal tabs fully readable
OutThisLife Jun 29, 2026
dfb561a
refactor(desktop+dashboard): extract shared WebSocket/JSON-RPC layer
OutThisLife Jun 29, 2026
6c52e4a
fix(desktop): match agent terminal scrollback to user tabs
OutThisLife Jun 29, 2026
5a4bdfd
fix(shared): close websocket clients deterministically
OutThisLife Jun 29, 2026
6776b2f
feat(desktop): live gateway popout + statusbar/command-center polish
OutThisLife Jun 29, 2026
f6ccf08
refactor(web): centralize dashboard websocket URL calls
OutThisLife Jun 29, 2026
5a2906a
chore(desktop): keep the diff surgical
OutThisLife Jun 29, 2026
216ace4
style(shared): apply workspace formatter to websocket helpers
OutThisLife Jun 29, 2026
83f09f5
Merge pull request #54558 from NousResearch/bb/overlay-panels
OutThisLife Jun 29, 2026
1a1e00f
fix(desktop): stop injecting ctrl-l into terminal startup
OutThisLife Jun 29, 2026
ae465e9
Merge branch 'main' of github.com:NousResearch/hermes-agent into bb/d…
OutThisLife Jun 29, 2026
e9b95df
fix(docker): include apps/shared in dashboard image build
OutThisLife Jun 29, 2026
f019a99
docs: clarify desktop is self-contained, not dependent on the dashboard
OutThisLife Jun 29, 2026
4488fe1
Merge pull request #54517 from NousResearch/bb/desktop-multiterminal
OutThisLife Jun 29, 2026
dff491a
feat(cli): add headless `hermes serve` backend; desktop no longer lau…
OutThisLife Jun 29, 2026
e684b80
fix(desktop): route old runtimes through `dashboard` when `serve` is …
OutThisLife Jun 29, 2026
9d9a50c
test(cli): pin the `hermes serve` decoupling contract
OutThisLife Jun 29, 2026
1c0fa12
feat(desktop): persist & restore terminal tabs + scrollback across re…
OutThisLife Jun 29, 2026
306b661
fix(agent): limit .hermes.md parent walk to git repos only
aaronlab Jun 29, 2026
14204b0
test(agent): cover .hermes.md no-git-root cwd-only behavior
teknium1 Jun 29, 2026
43eaf79
chore: remove committed PR infographics and gitignore the path (#54564)
teknium1 Jun 29, 2026
163562b
fix: normalize lmstudio base urls
lkevincc0 Jun 26, 2026
9cf9d3a
chore(release): add AUTHOR_MAP entry for PR #53295 salvage
teknium1 Jun 29, 2026
d836b2b
fix(matrix,mattermost): invite auth check + API path traversal guard
aaronlab Jun 29, 2026
e20ff35
test(matrix): authorize inviter in DM-invite fixture for new invite-a…
teknium1 Jun 29, 2026
0943e2a
fix(cron): don't report a false 'gateway not running' on external-pro…
benbarclay Jun 29, 2026
313a8c6
fix(skills): replace string prefix check with strict path containment
Ruzzgar Apr 10, 2026
1af109c
test(cli): drop pytest dep + use real sentinel handlers in serve test
OutThisLife Jun 29, 2026
fb0644f
Merge pull request #54585 from NousResearch/bb/desktop-terminal-history
OutThisLife Jun 29, 2026
388268e
Merge pull request #54568 from NousResearch/bb/shared-websocket-layer
OutThisLife Jun 29, 2026
e1f4098
docs(cron): document explicit per-channel delivery targets for all pl…
benbarclay Jun 29, 2026
8fe800e
fix(file-tools): sanitize host/relative cwd override before it reache…
benbarclay Jun 29, 2026
476875a
Add dashboard backup upload and download
shannonsands Jun 29, 2026
61a4526
fix(gateway): clear session-scoped model overrides on /resume
Junass1 Jun 29, 2026
b4300f2
fix(gateway): evict cached agent on auto-reset to prevent stale conte…
marco0158 Apr 16, 2026
0b733a8
test(gateway): pin auto-reset cached-agent eviction (#10710)
teknium1 Jun 29, 2026
74541be
fix(security): cap WeCom callback body size before pre-auth XML parse…
teknium1 Jun 29, 2026
98a7cfb
fix(logging): suppress Windows lock timeout tracebacks
helix4u Jun 28, 2026
25d35cc
infographic: Windows CLH lock-timeout traceback suppression (#54436 s…
teknium1 Jun 29, 2026
032d702
fix(agent): omit stream_options for native Gemini streaming
sgaofen Jun 29, 2026
0106082
fix(agent): return OpenAI-shaped copilot ACP tool calls
sgaofen Apr 23, 2026
b481348
fix(agent): stream copilot ACP chat completions
sgaofen Apr 23, 2026
d7e573e
fix(vision): detect Ollama vision models via /api/show (#54511)
HexLab98 Jun 29, 2026
23f245e
test(vision): cover Ollama /api/show vision capability routing (#54511)
HexLab98 Jun 29, 2026
1c75e7c
feat(dashboard): list & add arbitrary custom .env keys on the Keys page
benbarclay Jun 29, 2026
20b03d9
i18n: add Custom Keys strings to all locale files
benbarclay Jun 29, 2026
aa2ae36
fix(desktop): launch Windows backend as console python so child conso…
jquesnelle Jun 29, 2026
f860492
test(desktop): match multiline spawn(ps, fullArgs) via regex like sib…
teknium1 Jun 29, 2026
1289f12
fix(memory): lazy-install supermemory + mem0 SDKs like honcho/hindsight
benbarclay Jun 29, 2026
0434a9a
chore: regenerate uv.lock for supermemory + mem0 extras
teknium1 Jun 29, 2026
29f0968
test(windows): harden pid-scan no-window assertion against captured-c…
teknium1 Jun 29, 2026
4125cc3
fix(slack): subscribe to message.mpim + mpim scopes so group DMs work
benbarclay Jun 29, 2026
34e616e
feat(slack): nudge stale installs to add mpim scopes; mark message.mp…
teknium1 Jun 29, 2026
2704563
fix(tools): send listItemId instead of sessionKey in Camofox tab crea…
liuhao1024 Jun 3, 2026
babd916
fix(browser): send Authorization header in Camofox HTTP calls when CA…
liuhao1024 May 6, 2026
fe38d50
fix(tools): read browser.command_timeout in Camofox HTTP client
liuhao1024 Jun 6, 2026
08d6195
fix(camofox): auto-recover from stale tab 404 on navigate
kaishi00 Jun 29, 2026
41095fd
fix(camofox): register CAMOFOX_API_KEY in OPTIONAL_ENV_VARS
teknium1 Jun 29, 2026
115e78c
test(camofox): accept headers= kwarg in persistence test mocks
teknium1 Jun 29, 2026
eddfecd
fix(vision): cap vision_analyze fan-out concurrency process-wide
benbarclay Jun 29, 2026
75317d8
fix(vision): narrow the fan-out cap to the CPU encode burst only
teknium1 Jun 29, 2026
bf0d8fe
fix(config): v32 migration flips baked-in verify_on_stop=true to fals…
teknium1 Jun 29, 2026
c79e6bc
fix(browser_tool): resolve race in _get_command_timeout cache returni…
Sanjays2402 Jun 29, 2026
9f97915
fix(browser): route open-timeout base through _safe_command_timeout
teknium1 Jun 29, 2026
fa11b11
fix: propagate key_env from custom_providers into ProviderDef
telos-oc Apr 23, 2026
2f5950a
chore(release): add telos-oc to AUTHOR_MAP for PR #14353 salvage
teknium1 Jun 29, 2026
d5eee13
perf(profiles): fix list_profiles O(N*M) wrapper rescan (6.4s -> 0.4s)
max-chen Jun 23, 2026
1bb7b59
fix: offload blocking profiles endpoints from asyncio event loop (#54…
Sahil-SS9 Jun 29, 2026
10c9eaf
chore(attribution): map mango001@126.com -> max-chen for salvaged #51194
teknium1 Jun 29, 2026
fa3dba4
docs(infographic): add list_profiles perf-fix infographic
teknium1 Jun 29, 2026
194bff0
fix(gateway): confirm final delivery before suppressing send
sgaofen Jun 29, 2026
dc5ef20
test(reasoning-floor): isolate stale-timeout floor tests from config-…
teknium1 Jun 29, 2026
f5ecbe1
feat(dashboard): auto-initiate portal SSO redirect on unauthenticated…
benbarclay Jun 29, 2026
61f56d2
refactor(dashboard-auth): drop redundant _interactive_providers helper
teknium1 Jun 29, 2026
23c03ce
fix(session-db): enrich NULL session metadata via upsert instead of I…
teknium1 Jun 29, 2026
576424c
fix(security): redact browser CDP endpoint logs
Ruzzgar Apr 5, 2026
9e49013
fix(security): fail-closed feishu webhook rate limiter + whatsapp bri…
Mibayy Jun 29, 2026
0fe9755
fix(gateway): use last_prompt_tokens for session-reset activity check
Mibayy Jun 29, 2026
cdd8e0a
test(gateway): exercise last_prompt_tokens in reset-activity tests
teknium1 Jun 29, 2026
1debd5e
fix(security): add session-id filename sanitizer to prevent path trav…
Xowiek Jun 29, 2026
ea1372d
fix(security): wire session-id sanitizer into artifact paths + API bo…
teknium1 Jun 29, 2026
09666ce
fix(gateway): neutralize untrusted session metadata in prompts
Xowiek Apr 7, 2026
dbad6d4
fix(gateway): also neutralize untrusted Matrix room name in prompt
teknium1 Jun 29, 2026
f53ba9b
fix(s6): dot-prefix gateway staging dir so svscan ignores it mid-buil…
benbarclay Jun 29, 2026
88e6f9b
fix(auxiliary): preserve max_tokens for NVIDIA NIM aux calls
HexLab98 Jun 29, 2026
f134529
test(auxiliary): cover NVIDIA NIM max_tokens in _build_call_kwargs
HexLab98 Jun 29, 2026
fd32456
feat(desktop): add context usage breakdown popover
austinpickett Jun 29, 2026
3bbeb9e
Merge pull request #54907 from NousResearch/austin/feat/context-usage…
OutThisLife Jun 29, 2026
c6c1fd8
docs: create dev venv outside the source tree (root-cause fix for #77…
teknium1 Jun 29, 2026
ee8cbfd
feat(web_extract): truncate-and-store instead of LLM summarization (#…
teknium1 Jun 29, 2026
7cf6758
feat(desktop): read-only spectator transcript for subagent watch windows
OutThisLife Jun 29, 2026
929dd9c
Merge pull request #55033 from NousResearch/bb/subagent-watch-readonly
OutThisLife Jun 29, 2026
7a6b3cb
fix(desktop): show Gateway statusbar tooltip via composed trigger Slots
OutThisLife Jun 29, 2026
ccc92c5
Merge pull request #55086 from NousResearch/fix/gateway-statusbar-too…
OutThisLife Jun 29, 2026
b963d32
feat(gateway): suppress home-channel shutdown broadcast on flagged dr…
benbarclay Jun 29, 2026
c6d6a1c
feat(desktop): add pet roam + motion/direction store signals
OutThisLife Jun 29, 2026
964ec68
feat(desktop): pick directional run row from travel direction
OutThisLife Jun 29, 2026
a8f1d9c
feat(desktop): add surface-aware pet wander loop
OutThisLife Jun 29, 2026
7d3c1d5
feat(desktop): wire roaming into the floating pet
OutThisLife Jun 29, 2026
4da744e
feat(desktop): let the pet perch on the status bar and profile rail
OutThisLife Jun 29, 2026
b72c9e1
feat(desktop): add pet roam opt-in toggle + i18n
OutThisLife Jun 29, 2026
57d98eb
fix(web): remove marketing backdrop stack for lighter dashboard shell
austinpickett Jun 26, 2026
10374bb
fix(web): theme terminal foreground and restore backdrop plugin slot
austinpickett Jun 29, 2026
1abf0c6
fix(web): polish dashboard sidebar chrome and model card menus
austinpickett Jun 29, 2026
dbe92b9
fix(web): confirm sidebar gateway restart and use DS checkboxes
austinpickett Jun 29, 2026
75d4aa9
fix(web): confirm sidebar Update Hermes before running
austinpickett Jun 29, 2026
0e2a5a3
feat(desktop): ground the roaming pet — sprite-paced walk + feet on s…
OutThisLife Jun 29, 2026
a1e699a
feat(desktop): roaming pet patrols the base of an open overlay
OutThisLife Jun 29, 2026
d417ffb
Merge pull request #55114 from NousResearch/bb/pet-roam
OutThisLife Jun 29, 2026
bff91f9
feat(desktop): type voice.auto_tts in desktop config
OutThisLife Jun 29, 2026
09abbf8
feat(desktop): mirror voice.auto_tts into an $autoSpeakReplies store
OutThisLife Jun 29, 2026
572c7db
feat(desktop): add read-replies-aloud composer strings
OutThisLife Jun 29, 2026
fcdc05c
feat(desktop): add auto-speak watcher hook
OutThisLife Jun 29, 2026
596b813
feat(desktop): add read-replies-aloud toggle and wire auto-speak
OutThisLife Jun 29, 2026
290fa7f
fix(gateway): skip confirmed-dead delivery targets (deleted groups, b…
teknium1 Jun 29, 2026
f171842
Merge pull request #55154 from NousResearch/bb/desktop-auto-speak-rep…
OutThisLife Jun 29, 2026
89daacb
test(gateway): cover AsyncSessionDB offload + raw-call guard (failing)
yoniebans Jun 29, 2026
ea26f22
feat(gateway): add AsyncSessionDB offload facade
yoniebans Jun 29, 2026
0896fac
fix(gateway): route SessionDB calls through AsyncSessionDB
yoniebans Jun 29, 2026
0a997aa
fix(gateway): route aliased SessionDB calls through AsyncSessionDB
yoniebans Jun 29, 2026
6735162
fix(gateway): offload the Telegram topic-recovery helper tree off the…
yoniebans Jun 29, 2026
d2ce2c8
test(gateway): assert interleaving safety of concurrent offloaded DB …
yoniebans Jun 29, 2026
f3d2dfb
fix(dashboard_auth): allow any http:// host in self-hosted OIDC redir…
spjoes Jun 29, 2026
3a55f66
refactor(relay): adopt scope_id wire key (guild_id → scope_id dual-re…
benbarclay Jun 30, 2026
9311661
fix(kanban): detect clean-exit workers reaped by init as protocol vio…
MosaIQ Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ runtime/

# ---------- Not needed inside the Docker image ----------

# Desktop app source (Tauri/Electron); never installed in the container
# Desktop app source (Tauri/Electron); never installed in the container.
# apps/shared is the dashboard↔desktop websocket helper and is linked from
# web/package.json as a file: workspace dep — keep it in the build context.
apps/
!apps/shared/
!apps/shared/**

# Test suite — not shipped in production images
tests/
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,9 @@ RELEASE_v*.md
# Desktop demo-run scratch output (hermes writes demo/*.txt during recorded
# walkthroughs). Throwaway artifacts, never part of the app.
apps/desktop/demo/

# PR infographics are rendered locally and embedded in PR descriptions via the
# image-provider (fal.media) URL — they are NEVER committed to the repo. The
# PR body is the archive. See the hermes-agent-dev skill's
# pr-infographic-workflow reference (storage rule + lapse #8 / #COMMIT-1).
infographic/
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ The dashboard embeds the real `hermes --tui` — **not** a rewrite. See `hermes

### Electron Desktop Chat App (`apps/desktop/`)

A **separate** chat surface from both the classic CLI and the dashboard's embedded TUI. It is an Electron + React + nanostore renderer (`@assistant-ui/react`) that talks to a `tui_gateway` backend over JSON-RPC (`requestGateway(method, params)`). It does NOT embed `hermes --tui` — it has its own composer, transcript, and slash-command pipeline. Route desktop bugs to the `hermes-desktop-app-work` skill, not `hermes-dashboard-work`.
A **separate** chat surface from both the classic CLI and the dashboard's embedded TUI. It is an Electron + React + nanostore renderer (`@assistant-ui/react`) that talks to a `tui_gateway` backend over JSON-RPC (`requestGateway(method, params)`). The WebSocket/JSON-RPC transport lives in the framework-agnostic `apps/shared` package (`@hermes/shared` — `JsonRpcGatewayClient` + WS URL helpers), which the web dashboard (`web/`) also consumes; **desktop has no build/runtime dependency on the dashboard frontend** — it spawns a headless `hermes serve` backend server (the same gateway `dashboard` serves, minus the browser UI). `dashboard` and `serve` share `cmd_dashboard`/`start_server` but are independent surfaces — neither launches the other. The one exception is a backward-compat *fallback*: `serve` is newer, so the desktop spawn (`electron/backend-command.cjs` + `backendSupportsServe()` in `main.cjs`) detects whether the resolved runtime registers `serve` and, only when it does not (an older managed install / PATH `hermes` the app hasn't updated yet), rewrites the argv to the legacy `dashboard --no-open`. Without that, a new app against an un-upgraded runtime would crash on an unknown subcommand and brick every mid-upgrade user. It does NOT embed `hermes --tui` — it has its own composer, transcript, and slash-command pipeline. Route desktop bugs to the `hermes-desktop-app-work` skill, not `hermes-dashboard-work`.

**Slash commands in the desktop app are curated client-side, then dispatched to the backend.** The pipeline:

Expand Down
13 changes: 10 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,20 @@ this way, make sure you run the `hermes` entrypoint from this venv; running the
system `python3 -m hermes_cli.main` can pick up unrelated system Python
packages.

Create the venv **outside** the cloned source tree. A venv that lives inside
the directory the agent operates from can be wiped by a relative-path command
the agent runs against its own checkout (`rm -rf venv`, `uv venv venv`, etc.),
which silently destroys the running runtime mid-session. Keeping it outside the
tree means no relative path from the workspace resolves to it.

```bash
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent

# Create venv with Python 3.11
uv venv venv --python 3.11
export VIRTUAL_ENV="$(pwd)/venv"
# Create venv with Python 3.11, OUTSIDE the source tree
uv venv ~/.hermes/venvs/hermes-dev --python 3.11
export VIRTUAL_ENV="$HOME/.hermes/venvs/hermes-dev"
export PATH="$VIRTUAL_ENV/bin:$PATH"

# Install with all extras (messaging, cron, CLI menus, dev tools)
uv pip install -e ".[all,dev]"
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ COPY package.json package-lock.json ./
COPY web/package.json web/
COPY ui-tui/package.json ui-tui/
COPY ui-tui/packages/hermes-ink/ ui-tui/packages/hermes-ink/
# apps/shared/ is copied IN FULL because web/package.json references it as a
# `file:` workspace dependency (same pattern as hermes-ink above).
COPY apps/shared/ apps/shared/

# `npm_config_install_links=false` forces npm to install `file:` deps as
# symlinks instead of copies. This is the default since npm 10+, which is
Expand Down Expand Up @@ -184,6 +187,7 @@ RUN uv sync --frozen --no-install-project --extra all --extra messaging --extra
# invalidate the (relatively slow) web + ui-tui build layer.
COPY web/ web/
COPY ui-tui/ ui-tui/
COPY apps/shared/ apps/shared/
RUN cd web && npm run build && \
cd ../ui-tui && npm run build

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,14 @@ scripts/run_tests.sh
Manual clone fallback (for throwaway clones/CI where you intentionally do not
want the managed install layout):

Create the venv outside the cloned source tree — a venv inside the directory
the agent operates from can be wiped by a relative-path command the agent runs
against its own checkout, destroying the running runtime mid-session.

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv .venv --python 3.11
source .venv/bin/activate
uv venv ~/.hermes/venvs/hermes-dev --python 3.11
source ~/.hermes/venvs/hermes-dev/bin/activate
uv pip install -e ".[all,dev]"
scripts/run_tests.sh
```
Expand Down
6 changes: 5 additions & 1 deletion agent/agent_runtime_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,11 @@ def dump_api_request_debug(
dump_payload["error"] = error_info

timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
dump_file = agent.logs_dir / f"request_dump_{agent.session_id}_{timestamp}.json"
# Sanitize the session ID into a traversal-free path segment — it can
# originate from untrusted input (X-Hermes-Session-Id header), and an
# unsanitized "../"-shaped ID would write the dump outside logs_dir.
safe_sid = _ra()._safe_session_filename_component(agent.session_id)
dump_file = agent.logs_dir / f"request_dump_{safe_sid}_{timestamp}.json"

# Redact secrets before persisting/printing. This dump captures the
# full request body (system prompt, tool defs, context-embedded
Expand Down
16 changes: 15 additions & 1 deletion agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5489,10 +5489,24 @@ def _build_call_kwargs(
# ``/anthropic`` endpoint reached through the OpenAI SDK wrapper), where
# max_tokens is a MANDATORY field — omitting it is a hard 400. Keep it only
# there.
#
# NVIDIA NIM (integrate.api.nvidia.com and local NIM endpoints) is a
# second exception: some models—notably minimaxai/minimax-m3—return HTTP
# 200 with an empty choices[] payload when max_tokens is omitted. The main
# NVIDIA chat path already sends an output cap via the provider profile;
# preserve it on the auxiliary path too.
_effective_base = base_url or (
_current_custom_base_url() if provider == "custom" else ""
)
if _is_anthropic_compat_endpoint(provider, _effective_base):
_provider_norm = str(provider or "").strip().lower()
_is_nvidia_nim = (
_provider_norm in {"nvidia", "nvidia-nim", "nim", "build-nvidia", "nemotron"}
or base_url_host_matches(_effective_base, "integrate.api.nvidia.com")
)
if (
_is_anthropic_compat_endpoint(provider, _effective_base)
or _is_nvidia_nim
):
kwargs["max_tokens"] = max_tokens

if tools:
Expand Down
20 changes: 18 additions & 2 deletions agent/chat_completion_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from hermes_cli.timeouts import get_provider_request_timeout, get_provider_stale_timeout
from hermes_constants import PARTIAL_STREAM_STUB_ID, FINISH_REASON_LENGTH
from agent.error_classifier import FailoverReason
from agent.gemini_native_adapter import is_native_gemini_base_url
from agent.model_metadata import is_local_endpoint
from agent.message_sanitization import (
_sanitize_surrogates,
Expand Down Expand Up @@ -1911,14 +1912,21 @@ def _call_chat_completions():
stream_kwargs = {
**api_kwargs,
"stream": True,
"stream_options": {"include_usage": True},
"timeout": _httpx.Timeout(
connect=_conn_cap,
read=_stream_read_timeout,
write=_base_timeout,
pool=_conn_cap,
),
}
# OpenAI's `stream_options={"include_usage": True}` drives usage
# accounting on OpenAI-compatible endpoints (incl. the Gemini OpenAI
# compat shim and aggregators like OpenRouter). Google's *native*
# Gemini REST endpoint rejects the keyword outright
# (`Completions.create() got an unexpected keyword argument
# 'stream_options'`), so omit it only for that endpoint.
if not is_native_gemini_base_url(agent.base_url):
stream_kwargs["stream_options"] = {"include_usage": True}
request_client = _set_request_client(
agent._create_request_openai_client(
reason="chat_completion_stream_request",
Expand Down Expand Up @@ -2319,7 +2327,15 @@ def _call_anthropic():
_fire_first_delta()
agent._fire_reasoning_delta(thinking_text)

# Return the native Anthropic Message for downstream processing
# Return the native Anthropic Message for downstream processing.
# If the stream was interrupted (the event loop broke out above on
# agent._interrupt_requested), do NOT call get_final_message() — on
# a partially-consumed stream the SDK may hang draining remaining
# events or return a Message with incomplete tool_use blocks (partial
# JSON in `input`). The outer poll loop raises InterruptedError, so
# this return value is discarded anyway.
if agent._interrupt_requested:
return None
return stream.get_final_message()

def _call():
Expand Down
156 changes: 156 additions & 0 deletions agent/context_breakdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""Live session context-window breakdown for UI surfaces.

Estimates how the next provider request is composed: system prompt tiers,
tool schemas, and conversation history. Uses the same rough char/4 heuristic
as ``agent.model_metadata.estimate_request_tokens_rough`` so numbers align
with compression thresholds — not exact tokenizer counts.
"""

from __future__ import annotations

import json
import re
from typing import Any, Dict, List, Optional, Sequence, Tuple

_SKILLS_BLOCK_RE = re.compile(r"<available_skills>.*?</available_skills>", re.DOTALL)

_SUBAGENT_TOOL_NAMES = frozenset({"delegate_task"})

_CATEGORY_COLORS = {
"system_prompt": "var(--context-usage-system)",
"tool_definitions": "var(--context-usage-tools)",
"rules": "var(--context-usage-rules)",
"skills": "var(--context-usage-skills)",
"mcp": "var(--context-usage-mcp)",
"subagent_definitions": "var(--context-usage-subagents)",
"memory": "var(--context-usage-memory)",
"conversation": "var(--context-usage-conversation)",
}


def _chars_to_tokens(text: str) -> int:
if not text:
return 0
return (len(text) + 3) // 4


def _json_tokens(value: Any) -> int:
if not value:
return 0
return _chars_to_tokens(json.dumps(value, ensure_ascii=False))


def _tool_name(tool: dict) -> str:
fn = tool.get("function") if isinstance(tool, dict) else None
if isinstance(fn, dict):
return str(fn.get("name") or "")
return str(tool.get("name") or "")


def _split_tools(tools: Sequence[dict]) -> Tuple[List[dict], List[dict], List[dict]]:
builtin: List[dict] = []
mcp: List[dict] = []
subagent: List[dict] = []
for tool in tools:
name = _tool_name(tool)
if name.startswith("mcp_"):
mcp.append(tool)
elif name in _SUBAGENT_TOOL_NAMES:
subagent.append(tool)
else:
builtin.append(tool)
return builtin, mcp, subagent


def _memory_blocks(agent: Any) -> Tuple[str, str]:
memory_block = ""
user_block = ""
store = getattr(agent, "_memory_store", None)
if store is None:
return memory_block, user_block
try:
if getattr(agent, "_memory_enabled", True):
memory_block = store.format_for_system_prompt("memory") or ""
if getattr(agent, "_user_profile_enabled", True):
user_block = store.format_for_system_prompt("user") or ""
except Exception:
pass
return memory_block, user_block


def _strip_blocks(text: str, *blocks: str) -> str:
out = text
for block in blocks:
if block:
out = out.replace(block, "")
return out.strip()


def compute_session_context_breakdown(
agent: Any,
messages: Optional[List[dict]] = None,
) -> Dict[str, Any]:
"""Return a Cursor-style context usage breakdown for one live agent."""
from agent.model_metadata import estimate_messages_tokens_rough
from agent.system_prompt import build_system_prompt_parts

parts = build_system_prompt_parts(agent)
stable = parts.get("stable", "") or ""
context = parts.get("context", "") or ""
volatile = parts.get("volatile", "") or ""

skills_match = _SKILLS_BLOCK_RE.search(stable)
skills_index = skills_match.group(0) if skills_match else ""

memory_block, user_block = _memory_blocks(agent)
memory_text = "\n\n".join(part for part in (memory_block, user_block) if part).strip()

system_core = _strip_blocks(stable, skills_index)
system_tail = _strip_blocks(volatile, memory_block, user_block)
system_prompt_text = "\n\n".join(part for part in (system_core, system_tail) if part).strip()

tools = list(getattr(agent, "tools", None) or [])
builtin_tools, mcp_tools, subagent_tools = _split_tools(tools)

conversation_tokens = estimate_messages_tokens_rough(messages or [])

categories = [
("system_prompt", "System prompt", _chars_to_tokens(system_prompt_text)),
("tool_definitions", "Tool definitions", _json_tokens(builtin_tools)),
("rules", "Rules", _chars_to_tokens(context)),
("skills", "Skills", _chars_to_tokens(skills_index)),
("mcp", "MCP", _json_tokens(mcp_tools)),
("subagent_definitions", "Subagent definitions", _json_tokens(subagent_tools)),
("memory", "Memory", _chars_to_tokens(memory_text)),
("conversation", "Conversation", conversation_tokens),
]

estimated_total = sum(tokens for _, _, tokens in categories)

comp = getattr(agent, "context_compressor", None)
context_max = int(getattr(comp, "context_length", 0) or 0) if comp else 0
measured_used = int(getattr(comp, "last_prompt_tokens", 0) or 0) if comp else 0
context_used = measured_used if measured_used > 0 else estimated_total
context_percent = (
max(0, min(100, round(context_used / context_max * 100)))
if context_max
else 0
)

return {
"categories": [
{
"color": _CATEGORY_COLORS.get(category_id, "var(--ui-text-tertiary)"),
"id": category_id,
"label": label,
"tokens": tokens,
}
for category_id, label, tokens in categories
if tokens > 0
],
"context_max": context_max,
"context_percent": context_percent,
"context_used": context_used,
"estimated_total": estimated_total,
"model": getattr(agent, "model", "") or "",
}
4 changes: 2 additions & 2 deletions agent/context_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,9 @@ async def _fetch_url_content(
async def _default_url_fetcher(url: str) -> str:
from tools.web_tools import web_extract_tool

raw = await web_extract_tool([url], format="markdown", use_llm_processing=True)
raw = await web_extract_tool([url], format="markdown")
payload = json.loads(raw)
docs = payload.get("data", {}).get("documents", [])
docs = payload.get("results", [])
if not docs:
return ""
doc = docs[0]
Expand Down
Loading