Commit graph

15709 commits

Author SHA1 Message Date
Teknium
eb6aa03609
feat(analytics): record auxiliary model usage per task in session accounting (#65537)
* feat(analytics): record auxiliary model usage per task in session accounting

Auxiliary LLM calls (vision, compression, title_generation, web_extract,
session_search, ...) discarded their token usage, leaving dashboard
analytics blind to aux model spend (issue #23270).

- hermes_state.py: session_model_usage gains a task PK dimension
  (''=main loop) via v22 table-rebuild migration (SQLite can't alter a
  PK); record_auxiliary_usage() writes per-(model,provider,task) deltas
  WITHOUT touching sessions counters (gateway overwrites those with
  absolute main-loop totals — folding aux in would double-count or be
  clobbered). Aux rows never inherit the session's main-loop route.
- agent/aux_accounting.py: ContextVar ambient accounting context
  (mirrors the portal_tags conversation context); record_aux_usage()
  normalizes usage via usage_pricing.normalize_usage, estimates cost,
  and is strictly best-effort. moa_reference/moa_aggregator excluded —
  conversation_loop already folds MoA usage+cost into the main delta.
- agent/auxiliary_client.py: _validate_llm_response is the recording
  chokepoint — every successful non-streaming aux response passes
  through it exactly once, sync and async, including fallback paths
  (model read from the response itself stays accurate across
  fallbacks).
- run_agent.py: run_conversation publishes/resets the accounting
  context; agent/title_generator.py republishes on its bare thread.
- hermes_cli/web_server.py: /api/analytics/usage folds aux rows into
  by_model (aux-only models finally appear) and adds a by_task
  summary; /api/analytics/models surfaces aux rows on the Models page.

Design per review of PR #62850 by @eeksock (thread-local + separate
auxiliary_usage table): rebuilt on ContextVar (async-safe — thread-local
cross-attributes concurrent coroutines on one event loop) and the
existing session_model_usage table instead of a parallel accounting
path, extended beyond vision to every aux task, and wired the analytics
endpoints so the dashboard actually shows it. Credit to @eeksock for
the approach and @tboatman for the detailed root-cause analysis.

* test(moa): match _validate_llm_response mock to new accounting-hint signature

* test(aux): accept accounting-hint kwargs in remaining _validate_llm_response mocks
2026-07-16 04:23:12 -07:00
HexLab98
c9c9bb33fc test(gateway): cover api_server multiplex /p/<profile>/ routing
Lock in profile resolution, the /p/{profile}/v1/models mirror that
clients hit, and profile-scoped model name advertisement.
2026-07-16 15:59:45 +05:30
HexLab98
7aa21e3362 fix(gateway): add api_server /p/<profile>/ multiplex routing
Docs and MultiplexConfigError already promise secondary profiles are
served through the shared listener's /p/<profile>/ prefix, but only the
webhook adapter registered those routes — api_server returned 404.
Mirror every HTTP route, validate the prefix, and scope agent runs /
session DB / model listing to the target profile.
2026-07-16 15:59:45 +05:30
kshitijk4poor
e0240d7bf7 chore: add marcelohildebrand to AUTHOR_MAP for PR #42346 salvage 2026-07-16 15:23:14 +05:30
Marcelo
1a323d608e feat: add LM Studio JIT load mode 2026-07-16 15:23:14 +05:30
kshitijk4poor
e844ea9f0b fix: follow-up for salvaged PR #65187 — add missing compression_state to 5th call site, force-redact error text at gateway boundaries 2026-07-16 14:34:03 +05:30
Gille
1e895f4c17 fix(context): harden compression failure feedback 2026-07-16 14:34:03 +05:30
Gille
577beeb9b9 fix(context): preserve missing-key compression history 2026-07-16 14:34:03 +05:30
Alex López
202ad1b8c9 fix(context): preserve transient quota retry behavior 2026-07-16 14:34:03 +05:30
Alex López
c72f4576b9 fix(context): preserve messages when summary quota is exhausted 2026-07-16 14:34:03 +05:30
teknium1
5f171e36ab chore(skills/mcp-oauth-remote-gateway): move to optional-skills + modernize
- Move skills/mcp/ -> optional-skills/mcp/ (niche remote-deployment
  workflow; bundled tier is for daily-driver skills)
- Frontmatter: description 421 -> 57 chars, add platforms gating
  [linux, macos] (bash pipelines + gateway hosts), credit Ben Barclay
  first in author
- Document the built-in flow's own escape hatches (paste-back prompt,
  ssh -N -L port-forward) as cheap first fallbacks before manual
  token surgery; scope the skill to no-TTY messaging-gateway contexts
- Frame execution through the terminal / execute_code tools
- Tests: tests/skills/test_mcp_oauth_remote_gateway_skill.py covers
  all four diagnostic branches, atomic --write persistence, 0600
  perms, httpx UA on the wire, no-secrets-in-stdout, and frontmatter
  invariants (9 passing)
- Regen auto-gen docs page + one-line catalog row + sidebar entry
  (scoped; unrelated generator drift reverted)
2026-07-16 01:28:32 -07:00
Ben Barclay
03885e0aa1 feat(skills): add mcp-oauth-remote-gateway skill
Add an optional skill for connecting OAuth-gated remote MCP servers
(Better Stack, Linear, Cloudflare, Datadog, Stripe, etc.) when Hermes
runs as a remote gateway, where the built-in browser OAuth flow cannot
capture the 127.0.0.1 callback.

Covers the manual RFC 7591 DCR + RFC 7636 PKCE + authorization_code
flow, writing tokens in Hermes' HermesTokenStorage schema, the
dashboard-first escalation path, and a diagnostic script + pitfalls
for refresh/session-revocation recovery.
2026-07-16 01:28:32 -07:00
Teknium
49a8c3f836 fix(terminal): stop writing the cwd temp file entirely
Follow-up for salvaged PR #63255: with LocalEnvironment._update_cwd
delegating to the stdout marker parser, the cwd temp file has zero
readers left. Drop the 'pwd -P > file' writes from the bootstrap and
_wrap_command so every command stops paying a pointless file write
(and stops littering temp dirs with hermes-cwd-*.txt).
2026-07-16 01:28:24 -07:00
LeonSGP43
0ccd05b78a fix(local): stop re-reading cwd marker file 2026-07-16 01:28:24 -07:00
Teknium
9420ad946a
fix(webhook): scope reuse_address=False to macOS only (#65482)
On Linux, SO_REUSEADDR only allows rebinding past TIME_WAIT (a second
live listener would need SO_REUSEPORT, which we never set), so
disabling it bought no protection there while making a quick gateway
restart fail to rebind for up to ~60s. Keep the BSD silent-split guard
on darwin, default semantics elsewhere.

E2E verified: dual-stack v4+v6 bind on one port, immediate rebind
after disconnect, and live-listener conflict still rejected.
2026-07-16 01:28:18 -07:00
Teknium
6ee40b65ba chore: add ya-nsh to AUTHOR_MAP 2026-07-16 01:28:13 -07:00
Teknium
5330b2cfad fix: use Windows-aware isabs for native paths under patched _IS_WINDOWS
Follow-up for salvaged PR #26790: on a POSIX host with _IS_WINDOWS
patched (test simulation), os.path.isabs rejects C:\Users\x and the
new relative-cwd recovery would mangle a perfectly absolute native
Windows path. Check ntpath.isabs first on the Windows branch.
2026-07-16 01:28:13 -07:00
threadhoard-vps
5458c76566 fix: normalize local terminal relative cwd 2026-07-16 01:28:13 -07:00
embwl0x
0f239f49c6 fix(gateway): stop systemd retries on fatal config 2026-07-16 01:19:29 -07:00
solyanviktor-star
f0e6daddce fix(tools): don't report platform-restricted toolsets as enabled
tools_disable_enable_command filters platform-restricted toolsets out of
toolset_targets and prints an error for each, but the success summary at
the end is built from the raw targets list and only excludes unknown
toolsets and failed MCP servers. Running e.g.

    hermes tools enable discord --platform telegram

prints the 'not available on platform' error followed by 'Enabled:
discord' for a toolset that was never written to the config.

Exclude restricted_targets from the success summary, matching how
unknown toolsets and failed MCP servers are already handled.

Two regression tests: a restricted toolset alone must not print
'Enabled', and a mixed allowed+restricted invocation must report only
the allowed toolset (both fail before the fix).
2026-07-16 01:17:11 -07:00
Teknium
9ce0e67f27 feat(portal): ambient conversation context entangles aux/MoA/delegate calls
Extends the conversation=<id> Portal tag (salvaged from PR #65183 by
@J-SUPHA) from main-loop-only to every LLM call in a conversation:

- agent/portal_tags.py: ContextVar-based conversation context.
  nous_portal_tags() falls back to the ambient id when no explicit
  session_id is passed, so every aux tag site (auxiliary_client,
  chat_completion_helpers summary path, web_tools) inherits the tag
  with zero per-call-site plumbing. Ambient id wins over explicit
  per-segment ids since it carries the lineage root.
- hermes_state.py: SessionDB.get_conversation_root() — public wrapper
  over the lineage walk; returns the ROOT session id, so one
  user-facing conversation keeps a single conversation= value across
  context-compression rotation, and delegate subagent trees tag as
  their parent conversation.
- run_agent.py: run_conversation() publishes the root id for the turn
  and resets it in finally. _conversation_root_id() resolves via
  _parent_session_id for subagents.
- agent/moa_loop.py: MoA reference fan-out workers now run under
  propagate_context_to_thread so advisor slots attribute to the acting
  conversation (also fixes approval-callback propagation on that path).
- agent/title_generator.py: bare title thread republishes the context
  from its session id (spawned after turn reset).

Tests: ContextVar semantics, cross-context isolation, thread-hop
propagation, lineage-root resolution incl. cycle guard.
2026-07-16 01:13:43 -07:00
Jai Suphavadeeprasit
156ea4ad89 test(providers): expect conversation tags in Nous summaries
Update max-iteration summary assertions to include the agent session ID now attached to Nous Portal requests.
2026-07-16 01:13:43 -07:00
Jai Suphavadeeprasit
c98de70c2b test(providers): update Nous parity test for conversation tag
The end-to-end _build_api_kwargs parity test asserted the Nous Portal
tags exactly equal the base two-tag list. With the per-session
conversation tag, a real agent (which has a session_id) now emits a
third `conversation=<session_id>` tag. Assert against
nous_portal_tags(session_id=agent.session_id) so the check stays exact.
2026-07-16 01:13:43 -07:00
Jai Suphavadeeprasit
479d1aff6c init 2026-07-16 01:13:43 -07:00
Teknium
f8bf40b18b fix(photon): hide the npm dep self-heal console flashes on Windows too
Widen @lEWFkRAD's sidecar-headless fix (PR #54565) to the sibling spawn
sites: the npm ci / npm install self-heal runs in _reinstall_sidecar_deps
also popped a brief console window per run on Windows. Same
windows_hide_flags() helper (CREATE_NO_WINDOW only, so capture_output
stays usable).
2026-07-16 01:03:43 -07:00
Jeff Watts
d68ac9092a test(photon): cover hidden Windows sidecar spawns 2026-07-16 01:03:43 -07:00
Jeff Watts
d8f7b608c9 fix(photon): launch the iMessage sidecar headless on Windows
plugins/platforms/photon/adapter.py launches the Node sidecar (and the
spectrum-ts mixed-attachment patch run) via subprocess without creationflags.
On Windows this opens a visible console window on every sidecar (re)start --
and because a failed sidecar is retried on a timer, it flashes repeatedly.

Wire windows_hide_flags() (hermes_cli/_subprocess_compat) into both spawns,
the same helper the discord and whatsapp adapters already use for their
sidecar spawns -- photon was the one platform adapter this pattern missed.
CREATE_NO_WINDOW only (no DETACHED_PROCESS) so the persistent sidecar's
stdin/stdout pipes stay usable for the supervisor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 01:03:43 -07:00
Shannon Sands
4a69a6620e test(dashboard): use valid Telegram tokens in profile tests 2026-07-16 00:21:01 -07:00
Shannon Sands
d1be769b45 feat(dashboard): clarify manual Telegram bot setup 2026-07-16 00:21:01 -07:00
Shannon Sands
3ffd8b3da0 fix(dashboard): persist Discord toolsets to Discord platform 2026-07-16 00:20:33 -07:00
ethernet
c80b244b52 refactor(terminal,file-tools): delete legacy env-side cwd tracking (step 4)
The per-session record store is now the ONLY cwd mechanism. Deleted:

- env.cwd_owner stamping + prev_owner threading (terminal_tool): the
  shared env no longer carries ownership metadata at all
- _resolve_command_cwd's env/prev_owner params: resolution is
  workdir > session record > config/override default
- file_tools._live_cwd_if_owned + _get_live_tracking_cwd: path
  resolution never consults the shared env's live cwd
- file_tools._last_known_cwd + _remember_last_known_cwd +
  _last_known_cwd_for: the #26211 preserved-anchor registry is
  subsumed by the session record, which never lived on the env and
  therefore cannot be lost to env cleanup. The _get_file_ops
  stale-cache rescue now writes the record instead.
- env recreation (both _get_file_ops and terminal_tool) seeds the
  fresh env from override > session record > config

Why no transition fallback: the legacy state was process-local and
in-memory exactly like the record store — after a restart both start
empty, and within a running process every legacy write site has been
dual-writing the record since step 1. There is no populated-legacy/
empty-record state to fall back for.

Tests updated to drive the record store instead of the deleted
mechanism; the cross-session isolation suite now asserts the same
behavior contracts (no leak, cd isolation, #26211 persistence)
against the new architecture, plus a new "session C inherits nothing"
case that the old ownership guard could not express.
2026-07-16 00:18:38 -07:00
ethernet
4d30b05d6d chore(terminal): drop reference to untracked local plan file 2026-07-16 00:18:38 -07:00
ethernet
fd3d9c63d0 refactor(terminal): resolve command cwd from per-session records (step 3)
Third step of the cwd rearchitecture: _resolve_command_cwd now prefers
the session's own cwd record over the shared env's live cwd.

New resolution order: workdir > session record > legacy env.cwd
(ownership-gated, transition-only) > config/override default.

The record is written after every completed command for the session, so
it IS the session's cd state — another session's cd lands in another
record and cannot affect this session's commands. The legacy env.cwd
branch only fires for a session with no record yet (no command has
completed since this code loaded); it keeps the prev_owner ownership
guard for that transition window and is deleted in step 4 along with
env.cwd_owner stamping and file_tools' _last_known_cwd machinery.

Adds command-path regression tests including the terminal sibling of
the leak-A scenario (unowned shared env cwd vs session record) and an
E2E cd round-trip through terminal_tool.
2026-07-16 00:18:38 -07:00
ethernet
5461e0e098 refactor(file-tools): resolve paths against per-session cwd records (step 2)
Flips the read side of the cwd rearchitecture onto the _session_cwd
store introduced in the previous commit.

_authoritative_workspace_root now resolves:
  1. the session's own cwd record (get_session_cwd) — per-session by
     construction, so one session's cd can never leak into another
     session's file resolution, with no ownership heuristics at all
  2. registered override (fallback for cleared/never-written records)
  3. legacy shared-env live cwd + preserved anchor (transition-only,
     for commands that ran before this code loaded)
  4. sentinel-free absolute TERMINAL_CWD

delegate_task children get their record seeded from the parent's at
spawn: they keep starting in the parent's directory (current behavior)
but their subsequent cds stay isolated in their own record instead of
bleeding back through the shared env.

The wrong-worktree leak class is now solved structurally on this path —
there is no shared cwd for sessions to inherit. The legacy env-side
tracking (cwd_owner, _live_cwd_if_owned, _last_known_cwd) remains only
as a transition fallback and is deleted in the next step.
2026-07-16 00:18:38 -07:00
ethernet
be2a1290de refactor(terminal): introduce per-session cwd records (step 1: dual-write)
First step of the cwd rearchitecture (see PR #65185 for the targeted
leak fixes this will eventually supersede, and
.hermes/plans/cwd-rearch-audit.md for the full audit + sequencing).

The root cause of the wrong-worktree bug class is that cwd lives on the
SHARED terminal env — a global mutable timeshared between sessions.
env.cwd_owner stamping, _last_known_cwd, and file_tools' ownership
ladder are all patches over that misplacement.

This adds the replacement store: _session_cwd, keyed by the raw
session/task key, with record/get/clear accessors. Step 1 is dual-write
only — every site that learns a session's live cwd also records it:

- terminal_tool foreground path: after env.execute() the env's own
  post-command tracking has updated env.cwd; mirror it under the
  session key that drove the command
- register_task_env_overrides: a registered workspace cwd (ACP/TUI/
  desktop) seeds the session record
- clear_task_env_overrides: drops the record on teardown

Readers are untouched — behavior is identical. Later steps flip
file_tools resolution and _resolve_command_cwd to read this store,
then delete env-side tracking, cwd_owner, and _last_known_cwd.

Also hardens terminal_tool's env acquisition with an explicit
env-is-None guard (previously implicitly unbound on an unreachable
branch, flagged by pyright once the dual-write read env post-loop).
2026-07-16 00:18:38 -07:00
kshitijk4poor
92876effe2 fix(webhook): make dual-stack bind exclusive
Disable address reuse so an existing family-specific listener cannot silently split traffic with the webhook server. Normalize wildcard bind hosts for local CLI URLs and align setup documentation with the dual-stack default.
2026-07-16 12:36:51 +05:30
Ben
d542894adf fix(webhook): default to dual-stack bind so 6PN (IPv6) can reach the adapter
The webhook adapter defaulted to host='0.0.0.0' — IPv4 only. On Fly.io
hosted agents the edge router (hermes-agent-router) reverse-proxies public
webhook traffic to <app>.internal:8644 over 6PN, Fly's private network,
which is IPv6-only (.internal resolves to an fdaa:… address). An IPv4-only
listener is unreachable there, so public webhook POSTs to
https://<agent>.agents.nousresearch.com/webhooks/<route> never landed on
the adapter — the router's dial was refused.

Fix: DEFAULT_HOST = None, which makes aiohttp/asyncio create_server bind
BOTH address families. '::' is NOT a valid substitute: on hosts where the
kernel sets bindv6only=1 (verified on Fly machines) it yields an IPv6-only
socket, breaking the IPv4 loopback /health check and the AF_INET
port-conflict probe in connect(). None binds per-family regardless of the
sysctl. An explicit empty-string/null host in config now also normalises to
None (dual-stack) rather than an invalid host=''. Users can still pin a
specific host via platforms.webhook.extra.host.

Validated live on a Fly staging agent: with this default and no config
override, the adapter binds both v4 and v6 (127.0.0.1:8644 and [::1]:8644
both answer), and a public signed webhook POST through the router returns
202 (valid sig) / 401 (bad sig) instead of the router's 502.

Tests: new TestDualStackBind asserts the None default, config resolution
(missing/empty→None, pinned preserved), and a real dual-stack bind opens
both AF_INET and AF_INET6 listeners. Red-proof: these fail on the old
'0.0.0.0' default.
2026-07-16 12:36:51 +05:30
Teknium
b27d8b6ac8
feat(cli): promote Fireworks AI to #2 in the hermes model provider list (#65214)
Moves the fireworks entry in CANONICAL_PROVIDERS from its old slot
(after GMI Cloud) to directly below Nous Portal, ahead of OpenRouter.
Order propagates automatically to hermes model, the setup wizard,
Telegram /model, and the desktop provider catalog.
2026-07-15 23:57:39 -07:00
Teknium
094f2b55d3 chore: AUTHOR_MAP entry for 2751738943 (PR #54785 salvage) 2026-07-15 23:51:43 -07:00
Yingliang Zhang
ca803523ff test(tui): cover live notification ownership routing
Adapt the strongest #63317 live-loop handoff regression and cover lineage lookup failure plus addressed live-loop orphans.

Co-authored-by: Abhinav Bansal <abhibansal-sg@users.noreply.github.com>
2026-07-15 23:51:43 -07:00
2751738943
54d0948d38 fix(tui): route post-turn completions by owner
Apply positive-proof routing to every addressed notification in the registry and TUI poller while preserving ownerless legacy behavior and TUI delivery for poll-observed completions.

Remove the unused exact-key drain helper and cover ordinary success and failure, origin, compression-lineage, orphan, and poll-observed paths.

Complements NousResearch/hermes-agent#54785.
2026-07-15 23:51:43 -07:00
Yingliang Zhang
81fc24862c fix(tui): route bg process notifications to owning session, drop orphaned events
Two complementary fixes for cross-session background-process notification
leakage in the TUI/Desktop multi-session path (#42674, #35652).

1. Poller orphan guard: after _notification_event_belongs_elsewhere
   returns False, check whether the event has a non-empty session_key
   that differs from the current session.  If so the owner session is
   gone — drop the event instead of hijacking it into an unrelated
   session transcript.

2. Post-turn drain filter: the existing drain_notifications() pops every
   event from the global queue regardless of ownership.  Added
   _drain_owned_notifications() which applies the same ownership routing
   used by the poller (consume own, requeue foreign-live, drop orphan),
   and wired it into the post-turn safety drain.

Complementary to PR #42731 which addresses a separate code path in the
same bug class.  Together they close #42674.
2026-07-15 23:51:43 -07:00
Ben Barclay
2ea39daeb1
fix(gateway): share relay adapter in multiplex mode (#65366) 2026-07-16 14:30:42 +10:00
Ben Barclay
6a35f9e667
fix(container): keep named multiplex gateway slots down (#65368) 2026-07-16 14:30:05 +10:00
ethernet
f8ddf4fd86
feat(ci): semantic package-lock.json diff as an upserted PR comment (#65206)
git diff on a lockfile is unreadable: npm reorders entries, rewrites
integrity hashes, and moves packages between nesting levels, so a
one-line package.json bump produces a thousand-line textual diff.

scripts/ci/lockfile_diff.py instead parses the `packages` map out of
both versions of every tracked package-lock.json (via `git show`),
reduces each to {install path: version}, and set-diffs the maps —
reorder/hash churn vanishes, leaving only actual version movement
(added / removed / updated, with nested dedup copies tracked
separately).

The lockfile-diff workflow posts the result as a Markdown table in a
PR comment gated behind a hidden marker: subsequent pushes PATCH the
existing comment instead of stacking new ones, and a push that reverts
all lockfile changes updates the comment to say so. Advisory only —
never fails on findings; fork PRs (read-only token) degrade to a
warning.

Wired through the ci.yml orchestrator with a new npm_lock lane in
classify_changes.py (fails open on .github/ changes per the existing
contract).
2026-07-16 03:18:15 +00:00
ethernet
f8b6d381e2
fix(nix): dirty-tree wrapper bug + filtered rebuild scope + overlay alias (#65237)
* fix(nix): fold makeWrapper line continuations into optionalStrings

When rev == null (any dirty-tree build), the empty optionalString
expansion left the previous line's trailing backslash dangling onto a
blank line, ending the makeWrapper command early and running
`--suffix PYTHONPATH ...` as its own shell command (`--suffix: command
not found`, exit 127). Clean trees passed CI; dirty trees with
extraPythonPackages failed — exactly the path the NixOS module
exercises.

The continuation now lives inside each optionalString (" \\\n  --set
..."), so the makeWrapper chain stays intact whether or not the
optional flags expand.

Verified by building with rev = null + extraPythonPackages =
[ pyfiglet ]: wrapper builds, PYTHONPATH suffix lands inside the
makeWrapper call, wrapped `hermes --version` runs, and the collision
check still executes (certifi correctly rejected).

* perf(nix): filter derivation sources to shrink rebuild scope

Every derivation previously saw the whole repo, so any file change
rebuilt everything. Each derivation now gets a filtered src with only
the files it consumes:

- lib.nix: derive npm workspace topology from the root package.json
  `workspaces` globs (single source of truth — a new workspace member
  is picked up with zero nix edits). pythonSrc (cleanSourceWith)
  excludes the JS workspace trees, docs/website, docker/.github,
  tests, nix/, flake.nix/flake.lock, root docs, and skills/ +
  optional-skills/. importNpmLock reads from a fileset-filtered
  npmRoot (root manifests + member package.jsons only).
- mkNpmPassthru takes `dirs` — the workspace dirs the package
  contains — and builds a per-package fileset src from them. web and
  desktop include apps/shared (file: dep). One shared
  `nix run .#update-npm-lockfile` replaces the per-package
  update_*_lockfile bins that only existed inside build sandboxes.
- python.nix: release venv loads the uv2nix workspace from pythonSrc.
  The editable venv keeps an unfiltered ./.. root —
  mkEditablePyprojectOverlay calls lib.path.splitRoot, which rejects
  a cleanSourceWith set, and the editable install reads the live
  checkout anyway.
- hermes-agent.nix: skills ship exclusively via HERMES_BUNDLED_SKILLS
  / HERMES_OPTIONAL_SKILLS (same mechanism as Homebrew packaging;
  setup.py's _data_file_tree returns [] for missing dirs), so
  SKILL.md edits no longer rebuild the venv. optional-mcps stays in
  the wheel — pyproject.toml lists its manifests as explicit
  data-files. Bundled assets are symlinked instead of copied, making
  the wrapper drv near-instant when only an input changed.
  __pycache__ filtered from bundled skills.
- checks.nix: find -L through the new symlinks; assert
  optional-skills presence + HERMES_OPTIONAL_SKILLS in the wrapper.
- run_tests.sh: fall back to $HERMES_PYTHON when no local venv
  exists, guarded by an `import pytest` probe (HERMES_PYTHON from a
  wrapped hermes binary points at the release venv, which has no
  pytest — without the guard every test file dies with "No module
  named pytest" while the runner exits 0).

Verified: nix flake check exit 0; built .#default .#tui .#web
.#desktop; SKILL.md and flake.nix edits leave the venv drvPath
unchanged; .py edits leave the tui drvPath unchanged; .tsx edits
leave the venv drvPath unchanged (and do change the tui drv);
scripts/run_tests.sh runs 299 tests green through both the venv and
HERMES_PYTHON paths, and rejects a pytest-less HERMES_PYTHON.

* refactor(nix): overlay aliases the flake's own package instead of re-instantiating

The overlay previously re-called callPackage against the consumer's
nixpkgs (final), so pkgs.hermes-agent could be a different derivation
than nix build .#default and the NixOS module's default — an untested
build matrix against arbitrary consumer nixpkgs versions, for a
package whose Python side is uv2nix-locked anyway.

Now the overlay is a pure alias for the flake's own locked package:
one callPackage site (packages.nix), everything else references it.
.override { ... } still works — callPackage's makeOverridable travels
with the derivation.

Verified: direct drvPath == overlaid drvPath; .override produces a
distinct drv.

* fix(nix): dedupe extraPlugins assertions, replace MESSAGING_CWD with terminal.cwd

- Delete the duplicated extraPlugins duplicate-name assertions block
  (same assertion declared twice back to back).
- Stop setting the deprecated MESSAGING_CWD env var, which made the
  module trip hermes' own startup deprecation warning. The working
  directory is now injected as terminal.cwd into the generated
  config.yaml; cfg.settings wins via recursiveUpdate, and container
  mode maps to the in-container mount path.
2026-07-15 21:45:35 -04:00
Gille
a16ac37dd9
fix(tui): redraw dashboard after new session (#65239) 2026-07-15 21:23:08 -04:00
Ben Barclay
3f2a389c7e
fix(auth): apply newer hosted bootstrap session (#64612)
* fix(auth): apply newer hosted bootstrap session

* fix(auth): validate rebootstrap replacement seeds
2026-07-16 08:52:47 +10:00
Ben Barclay
5fc2d9e64d
docs(auth): scrub Fly.io host detail from quarantine-log comment (#60145)
hermes-agent is public/OSS; the forensic-logging comment in
_quarantine_nous_oauth_state named 'Fly' (the specific managed-hosting compute
provider) twice. Reword generically ('a hosted agent', 'a managed log drain may
be WARNING-only') — the behaviour is unchanged, only the comment. Follows the
same scrub applied to the boot re-seed helper (#59983) before merge; this one
slipped through in #59976.
2026-07-16 08:44:43 +10:00
ethernet
0c1adb4877
fix(ci): handle merge race in js-autofix poll loop (#65231)
When the bot PR auto-merges, main moves — which the poll loop detects
as 'main moved' and tries to close the PR. But the PR is already
merged, so gh pr close fails with an error.

Fix: when main moves, re-check PR state before closing. If it's
already MERGED, exit cleanly. Also make gh pr close non-fatal (|| true)
as a belt-and-suspenders guard against the same race on the other
close paths.
2026-07-15 17:51:34 -04:00