Commit Graph

27536 Commits

Author SHA1 Message Date
kshitijk4poor 25caae02c0 refactor(gateway): derive the compacting noisy-status match from the templates
Replaces the hand-transcribed regex fragment with re.escape of COMPACTION_STATUS
and COMPACTION_HEARTBEAT_STATUS, matching the COMPACTION_DONE_STATUS precedent
two lines down, so wording drift cannot desync the chat-platform gate.
2026-09-03 11:27:55 +05:30
kshitijk4poor c41525b290 fix(compression): heartbeat honours the quiet-engine contract and the single start line
Review follow-ups: the heartbeat opened a visible compacting phase even when
the context engine suppressed the routine start status (no terminal edge would
ever close it), and its start() emitted a second start line milliseconds after
the routine one (two chat messages on adapters without send_or_update_status).
Gate the client-visible heartbeat on the start status having been emitted, drop
the start-time emit, and route ticks through agent._emit_status so CLI print
and gateway filtering match every other compaction status.
2026-09-03 11:27:55 +05:30
kshitijk4poor 5684f56a5b chore: map JLHunzicker@gmail.com -> JackHunzicker (PR #98371 salvage) 2026-09-03 11:27:55 +05:30
kshitijk4poor 548e0ec923 fix(compression): route the heartbeat through the shared compaction status contract
The contributor heartbeat called status_callback("compacting", <ad-hoc text>).
Every other compaction status uses the "lifecycle" key: the TUI gateway
re-tags lifecycle statuses to kind="compacting" via
is_compaction_progress_status, Telegram edits one bubble per status key, and
gateway/run.py's chat-platform filter only recognises the registered
templates — so the ad-hoc text leaked to Telegram/Discord on every tick with
compression.progress_notices off (verified: _prepare_gateway_status_message
passed it through). Register COMPACTION_HEARTBEAT_STATUS next to
COMPACTION_STATUS, emit it under "lifecycle", and cover the filter.
2026-09-03 11:27:55 +05:30
Jack 8381d4fadf compression: emit periodic client-visible status during compaction
Context compression can stream for minutes with no deltas, tool events,
or status lines reaching remote transports. Idle-progress watchdogs on
those clients treat the silence as a dead turn and interrupt it — the
Android relay app's 180s turn watchdog fires session.interrupt, killing
a healthy compression mid-flight and rolling back its work. On sessions
near the context ceiling this loops forever: every new prompt retriggers
preflight compression, which dies at exactly +180s again (observed
telemetry: attempts aborted at 180469ms/180233ms/180219ms with
failure_class=explicit_interrupt).

Fix: the existing _CompressionActivityHeartbeat (which today only
refreshes the SessionDB activity tracker) now also emits a 'compacting'
status through agent.status_callback — once at compression start and on
every heartbeat tick. The gateway already routes status_callback to
status.update events, and clients already reset their watchdogs on any
received event, so each heartbeat re-arms them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6d2e0e860d6fdb44ce549d1fa11a06662b1a4b0b)
2026-09-03 11:27:55 +05:30
kshitijk4poor d6de054bac refactor(compressor): one helper for the last template-visible role
The re-append slot check duplicated the summary-role scan at the head/summary
boundary; both now call _last_template_visible_role.
2026-09-03 11:27:40 +05:30
kshitijk4poor ab73aeff05 fix(compressor): keep pending tool_calls, avoid double anchors, no header stacking
Review follow-ups on the in-flight replay:

- Run _sanitize_tool_pairs BEFORE the re-append. Its trailing-in-flight
  exemption (#79278) walks back from the list end; with the replay user row
  there, a genuinely pending assistant(tool_calls) looked orphaned and had its
  calls stripped, so the executor's late tool result was dropped.
- When the restatement is merged onto a user-pinned summary carrier, flag the
  carrier (_inflight_replay_merged). The carrier's metadata marks it synthetic,
  so conversation_compression._ensure_compressed_has_user_turn inserted a second
  copy of the same request; it now treats the flag as intent-present, and the
  next cycle recognises the carrier as the task instead of losing it.
- Header idempotency: restate the text after the last header so a task that
  survives several compactions carries one header and one copy.
- Exclude metadata-flagged scaffolding (_todo_snapshot_synthetic, recovery
  nudges) from the in-flight scan via the shared _is_real_user_message.

Tests cover all four; each fails with its fix reverted.
2026-09-03 11:27:40 +05:30
kshitijk4poor 1d260f7683 fix(compressor): judge the re-append slot on template-visible roles
The in-flight replay checked only compressed[-1] before choosing between
appending a user row and merging onto the summary carrier. A user-pinned
summary followed by an exempt tool_calls/tool tail therefore gained a second
visible user turn and broke the Mistral-style alternation pre-flight
(tests/agent/test_summary_role_template_alternation.py::test_zero_user_guard_still_forces_user).
Look through the exempt tail to the last template-visible role instead.
2026-09-03 11:27:40 +05:30
Justin Wilson 0ed3f9b45b fix(cron): re-append in-flight job prompt after compaction
Compaction of a single-prompt (cron) session left no user message after
the handoff, so SUMMARY_PREFIX ordered the model to do nothing and the
scheduler recorded success. Re-append the in-flight task after the
summary.

Fixes #100818

(cherry picked from commit c0ea50ac02fd7553b1ef7deb0162483cb9a1b6ac)
2026-09-03 11:27:40 +05:30
Teknium 4dac5f28af fix(execute_code): parallel cells no longer orphan kernel processes; LSP stops treating package dirs as project roots
Session kernels: a kernel mid-spawn (proc=None) read as dead, so every
concurrent cell for one owner replaced the registry entry and the
winner's process leaked outside the registry (110 live kernels, 1.1 GB,
330 threads under one 4-capped process). Reap/evict also tore down
kernels with cells attached, rmtree-ing the staging dir under the
spawner. Kernels now track attached cells: only settled kernels are
reaped/evicted, a kernel dropped while busy is torn down by its last
cell, and in-cell registry pops never remove a replacement.

LSP: a directory holding __init__.py is a package, not a project root.
hermes_cli/setup.py matched the python marker list and gave every
worktree a second pyright rooted at hermes_cli/ (70 of 105 reaped
clients in one session, ~40 servers / 8.7 GB live).
2026-09-02 21:50:00 -07:00
Teknium c368b145da chore: trim salvaged #101772 to the fix (2 invariant tests, drop cosmetic comment rewrites) 2026-09-02 21:49:23 -07:00
Zane Chee b1bf099f14 fix(computer-use): distinguish doctor and gateway environments 2026-09-02 21:49:23 -07:00
Teknium 139396995a feat(opencode): send x-opencode-session on every OpenCode request for backend affinity
OpenCode pins requests sharing an x-opencode-session value to one upstream
backend, which is what keeps its prompt cache warm across a conversation.
Hermes never sent it, so cache ratios on OpenCode traffic were poor.

- agent/opencode_affinity.py: single owner of the header — target detection
  (built-in zen/go/free, custom opencode-* providers, any opencode.ai URL)
  and the key (affinity scope → conversation root → session id, cron
  timestamp stripped), same resolution as OpenRouter/xAI affinity hints.
- build_api_kwargs: merged once after the per-mode builder, so
  chat_completions, codex_responses and anthropic_messages all carry it.
- auxiliary _build_call_kwargs: same key from the runtime-main session so
  compression/title/vision calls stay on the conversation's backend; the
  aux Codex and Anthropic adapters now forward extra_headers.

Closes #81584, #81832 (deepseek-v4-flash 400 without the header).
2026-09-02 21:46:36 -07:00
Beto de Paola db23c79bbe test(meta-ai): update fallback catalog expectation 2026-09-02 21:22:53 -07:00
Beto de Paola 77efca8b1d docs(meta-ai): trim live catalog comments 2026-09-02 21:22:53 -07:00
Beto de Paola f5fd317dcc docs(meta-ai): point contributor pricing to live page 2026-09-02 21:22:53 -07:00
Beto de Paola 83ecb6e695 feat(meta-ai): live-first model catalog + generic contributor warning
- Register meta-ai as a live-first picker provider so the /v1/models catalog
  leads the picker; new models appear without a PR
- Override fetch_models to exclude non-chat models (muse-image-*, muse-voice-*)
  from the picker; new chat model families pass through automatically
- Slim fallback_models to a single safety-net entry (muse-spark-1.2), shown
  only when the live fetch fails
- Make data-policy contributor warning model-generic (not hardcoded to 1.2)
  so it covers any future -contributor model
- Update test assertion to match generic warning text

LOCAL ONLY — pre-launch, not for push.
2026-09-02 21:22:53 -07:00
Teknium a2a16dfdac test(bot-relay): live-Bot-Chat deliver test ignores the server's import-time git prefetch
tui_gateway.server kicks off prefetch_update_check() at import, which runs
`git rev-parse`/`rev-list` on a daemon thread. The test patched
subprocess.run with a fake that returned None and recorded every call, so
whenever that thread landed inside the test window the assertion saw a
`git` argv and the thread crashed on `.returncode` (red on main since
6e775907d7 made the fetch slower). Filter to non-git spawns and return a
real-shaped proc for everything.
2026-09-02 21:22:07 -07:00
Teknium 116ca1db1e fix: sibling Nous 401 recovery adopts a peer's refresh instead of rotating again
The stampede fix added a `stale_access_token` hint to
resolve_nous_runtime_credentials() so a process whose bearer just 401'd
adopts a token a sibling already rotated instead of re-POSTing the shared
grant — but only the credential-pool caller passed it. The main agent's
401 path (run_agent._try_refresh_nous_client_credentials), the auxiliary
client rebuild, and the proxy adapter all called force_refresh=True with
no hint, so `_already_rotated_by_peer` could never fire: N subagents
hitting hourly expiry still issued N serialized refreshes, each one
invalidating the token a sibling had just adopted.

Live 12-process A/B against a fake Portal: 12 refresh POSTs / 9 distinct
final tokens before, 1 POST / 1 token after.
2026-09-02 21:22:07 -07:00
hermes-seaeye[bot] 97f3229dfd fmt(js): npm run fix on merge (#101836)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-03 03:17:48 +00:00
Jeffrey Quesnelle 22e6f9a428 Merge pull request #101823 from NousResearch/ethie/local-enable
enable local models by default on win32 and darwin
2026-09-02 23:08:17 -04:00
ethernet 75aaf56904 enable local models by default on win32 and darwin 2026-09-02 22:40:22 -04:00
Jeffrey Quesnelle 48c0c3a873 Merge pull request #96633 from afourniernv/chore/relay-0.8.0
fix(relay): upgrade to 0.8.3
2026-09-02 22:03:27 -04:00
kshitijk4poor 593aa74c61 test(state): pin that a v28 install still runs the trigram cron-exclusion migration
The salvaged #101266 gated its rebuild on current_version < 27; main had
already reached SCHEMA_VERSION 28 (column-reconciliation bumps), so on any
existing install the gate would never fire and cron rows would stay in the
trigram index forever. The cherry-pick resolution renumbers to v29; this
test seeds a v28 database and asserts the migration runs. Mutation-checked
against the original < 27 gate.
2026-09-03 05:08:22 +05:30
fangliquanflq ea65fcd980 perf(state): exclude cron sessions from trigram FTS 2026-09-03 05:08:22 +05:30
fangliquanflq 57162d0cc1 fix(state): bound FTS indexing for large tool results 2026-09-03 05:03:04 +05:30
Royalaid 48a00349bd test(search): make ignored-tree probe fixture portable 2026-09-03 04:56:59 +05:30
Royalaid 906cd5f443 fix(search): prune heavy trees from zero-match probe 2026-09-03 04:56:59 +05:30
Royalaid efd7277f0f fix(search): propagate order through code sandbox 2026-09-03 04:56:59 +05:30
Royalaid 73e116608e test(interrupt): prove publisher lock contention 2026-09-03 04:56:59 +05:30
Royalaid f81214307b test(interrupt): harden publisher cleanup 2026-09-03 04:56:59 +05:30
Royalaid f88bdb7086 test(interrupt): clear publisher thread state 2026-09-03 04:56:59 +05:30
Royalaid 4e5499c62e fix(search): close admission cancellation race 2026-09-03 04:56:59 +05:30
Royalaid 9e47ac17bd fix(search): serialize filename walks by root 2026-09-03 04:56:59 +05:30
Royalaid af98771b45 fix(search): normalize dash-prefixed find roots 2026-09-03 04:56:59 +05:30
Royalaid 3c864fe331 fix(search): terminate rg root options 2026-09-03 04:56:59 +05:30
Royalaid 092b355f90 fix(search): scope macOS globs and mark bounded totals 2026-09-03 04:56:59 +05:30
Royalaid 4c189cfd41 fix(search): accept bounded rg SIGPIPE and full SemVer 2026-09-03 04:56:59 +05:30
Royalaid 6d9022a55e fix(search): harden bounded fallback handling 2026-09-03 04:56:59 +05:30
Royalaid e11d91339d fix(search): fail closed and preserve explicit roots 2026-09-03 04:56:59 +05:30
Royalaid 06086b95e1 fix(search): unify fallback scans and reject partial errors 2026-09-03 04:56:59 +05:30
Royalaid 3880e4af62 fix(search): close ordering and resolver gaps 2026-09-03 04:56:59 +05:30
Royalaid 413fd2a1fc feat(search): add fast discovery file ordering 2026-09-03 04:56:59 +05:30
Royalaid aa446092d0 fix(file-ops): normalize Windows find fallback paths 2026-09-03 04:56:59 +05:30
Royalaid d21cfad3d1 test(file-ops): run subprocess helper through Git Bash 2026-09-03 04:56:59 +05:30
Christopher cf86c47624 fix(agent): evict stale screenshot payloads before send
Call the existing keep-newest vision retirement on the per-call
api_messages clone after sanitization so OpenAI-style tool-result
screenshots are not re-uploaded on every later turn.
2026-09-03 04:51:51 +05:30
Adolanium 7caee2898b perf(agent): stop rebuilding the streamed reply text on every delta
`_record_streamed_assistant_text` grew the turn's visible text with `+=`
on an attribute. Python only grows a string in place when the target is a
local variable, so this copied the whole text on every delta.

The loop runs once per streamed token, so a reply of length N costs about
N squared in copying. A 200 KB answer arriving in 4-character deltas
moves several billion characters and burns seconds of CPU in the loop the
file itself calls the hottest one in the agent.

The text is now kept as a list of pieces and joined when read. Reading
happens at turn end and on interrupt, not per delta, so the whole turn is
linear in the length of the reply. `_fire_stream_delta` used to join on
every token just to ask if the text was empty. That check now looks at
the parts list.

`_current_streamed_assistant_text` becomes a property over that list, so
the seven readers and the call sites that clear it between turns keep
working unchanged. Reading does not collapse the pieces, because a delta
landing between the join and the write back would be lost.

Measured with 8-character deltas: adding 20k deltas to an already long
text took 3.1 times as long as the first 20k before, and 0.9 times after.
2026-09-03 04:46:41 +05:30
John Paul Soliva 1a451bfaeb fix(mcp): pick the Windows launcher, not the sh script, from an npx cache
Review follow-up, and a real platform bug. On Windows npm's `.bin` holds three
siblings per binary — the extensionless sh script, `<name>.cmd` and
`<name>.ps1`. Spawning the sh one from a Windows process fails, and
`os.access(X_OK)` there is effectively an existence check, so the previous
candidate test could not tell them apart: it would have picked a file that
cannot run and broken server startup that works today via npx.

Select by extension instead (`.cmd`, then `.exe`), the same precedence
hermes_constants._candidate_node_command_names already uses for npm/npx/node,
and fall back to npx when no launcher is present.

The platform branch moved into `_npx_bin_candidates(..., windows=...)` so it is
testable by injection. Patching `os.name` instead took pytest's own traceback
formatting down with an INTERNALERROR — a test that cannot report its own
failure is worse than no test.

Also from review: an `npx pkg -y` shape (flag AFTER the spec) now falls back to
npx, since those args are forwarded verbatim and would hand the server a flag
npx would have consumed. And the structural OSV-ordering guard reports a rename
explicitly instead of raising a bare ValueError that reads like a broken test.
2026-09-03 04:40:44 +05:30
John Paul Soliva 11ed840431 perf(mcp): skip npx's resident parent when the package is already cached
`npx` resolves a package and then FORKS: it stays alive as the real MCP
server's parent for the whole process lifetime while doing no work. Measured
on a 4-agent host, that is ~48 MB of private memory per stdio MCP server —
and it buys nothing here, because Hermes already wraps the child in its own
parent-death watchdog, so npx's supervision is a second parent nobody reads.
The process tree for one server is watchdog -> `npm exec <pkg>` -> node.

When the package is already in npx's cache, spawn its binary directly and
drop the middle process. A cache miss changes nothing: the command stays
`npx`, so a cold machine still installs on first run.

The swap deliberately happens AFTER the OSV malware preflight.
`_infer_ecosystem` keys off the command basename being `npx`/`uvx`/`pipx`, so
rewriting first makes `check_package_for_malware` return None and silently
turns the gate into a no-op. Two tests pin that ordering — one behavioural,
one structural — because a future edit that moved the swap earlier would
disable the malware check without failing any test of either piece alone.
This is also why doing it in code beats the config workaround of pointing
`command` straight at the cached path: that loses the preflight.

Conservative by construction — falls back to npx for a version-pinned spec
(npx owns that resolution), an ambiguous or absent `bin` map, a missing or
non-executable binary, an unreadable cache entry, or no cache at all.

Measured on a Mac mini running four agent gateways plus a dashboard, ten
stdio MCP servers total: resident `npm exec` parents 5 -> 0, tracked
footprint 1531 MB -> 1324 MB across 30 -> 25 processes, free memory
464 MB -> 697 MB on a host that was actively swapping. Every MCP server kept
working; tool calls verified against a live Linear server afterwards.
2026-09-03 04:40:44 +05:30
RelaxJonh 6fdd93ab45 fix(tools): release per-task file registry state (#86514)
Clear file-operation trackers when a terminal task ends and remove per-path
locks after the last holder or waiter exits. This prevents long-lived gateway
processes from retaining file state indefinitely while preserving sibling-write
tracking and path-level serialization.

Fixes #86514
2026-09-03 04:34:45 +05:30