Upstream sync — merge Hermes upstream/main (3,189 commits)#16
Open
tomi204 wants to merge 3190 commits into
Open
Upstream sync — merge Hermes upstream/main (3,189 commits)#16tomi204 wants to merge 3190 commits into
tomi204 wants to merge 3190 commits into
Conversation
Keep empty-tail recovery scoped to the current stream segment and bound fallback flood retries. Preserve Telegram's server retry hint without blocking final delivery through a long cooldown.
…ions Copilot (api.githubcopilot.com/responses) binds replayed assistant codex_message_items ids to a specific backend "connection". Credential- pool rotation, a gateway restart, or routine load-balancer churn between turns all invalidate that binding, and Copilot rejects the stale id with HTTP 401 "input item ID does not belong to this connection" — even for short ids well under the #27038 64-char length cap, since this is a connection-scope problem, not a length problem. Once a session captures one of these ids it is persisted and replayed forever, permanently bricking the session. Thread an is_github_responses flag from build_kwargs/convert_messages into _chat_messages_to_responses_input and drop the id unconditionally on that path, mirroring how reasoning items already strip id on replay. phase/status/content are still replayed so cache-relevant signal isn't lost — only the connection-scoped id is unsafe to reuse. Written to apply independently of the #27038 length-cap fix so the two PRs don't block each other; they touch adjacent conditions in the same block and merge cleanly in either order. Fixes #32716 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_CodexCompletionsAdapter (agent/auxiliary_client.py) is a second, independent producer of Codex Responses input — used by auxiliary calls (context compression, flush_memories, MoA aggregation, session_search) that route through CodexAuxiliaryClient instead of the main agent's ResponsesApiTransport.build_kwargs. It calls _chat_messages_to_responses_input() directly without is_github_responses, so the previous commit's fix didn't cover it: an auxiliary call made against a Copilot-backed session could still replay a connection-scoped codex_message_items id and hit the same HTTP 401. Detect the Copilot host from the adapter's own client.base_url (same check the adapter already does further down for prompt_cache_key opt-out) and pass is_github_responses through, closing the gap. Still #32716. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Require literal booleans for backend-specific replay policy and pin non-default status and content preservation through both response paths.
Reapply the endpoint-aware preflight after request and execution middleware so no override can reintroduce a connection-scoped ID.
Exercise request and execution middleware replacements through the real conversation loop and assert the provider payload is sanitized.
… in fetch_models fetch_models() sends Authorization: Bearer <api_key> plus any default_headers (x-api-key etc.) via urllib.request.urlopen, and urllib's redirect handler forwards every header when following a 3xx — including to a different host. A catalog endpoint (or a compromised/misconfigured proxy in front of it) answering with a redirect to another origin therefore received the provider API key. Install an HTTPRedirectHandler that drops authorization, x-api-key, api-key, x-goog-api-key and cookie when the redirect target hostname differs from the original request, mirroring the pattern already used in skills/creative/comfyui/scripts/_common.py. Same-host redirects keep credentials so legitimate path-level redirects still work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g credentials Review feedback: a same-host redirect to a different port can land on a different service, which must not inherit the provider API key. Compare (scheme, hostname, effective port) — with 80/443 defaults — instead of hostname alone, and add a two-server regression test for the same-host/different-port case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`hermes -t web chat` silently dropped the toolset filter (and the same hold true for `-m`, `--provider`, `--tui`, `--dev` placed before `chat`). Reported in #28780 for `-t/--toolsets`; the others are sibling failures with the same root cause. Root cause: the chat subparser re-declared these flags with `default=None` (or `default=False` for store_true) on top of the matching top-level parser flags. When argparse dispatches into the subparser it shares the namespace via `dest`, so the subparser's default overwrites whatever the top-level parser parsed before the subcommand. `-s/--skills`, `-r/-c/-w`, `--yolo`, and `--pass-session-id` already use `default=argparse.SUPPRESS` for exactly this reason — the chat-subparser action becomes a no-op unless the user explicitly passes the flag after `chat`, and the parent value survives. Reproduction (origin/main, before fix): >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets None >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets 'web' After fix: >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets 'web' >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets 'web' Sibling flags fixed in the same commit because they share the exact same argparse pattern bug — verified via a new contract test that scans every chat-subparser action whose `dest` is also on the top-level parser and asserts `default is argparse.SUPPRESS`. The test fails on origin/main listing all five offenders and passes after this fix. Test additions in tests/hermes_cli/test_argparse_flag_propagation.py: - TestChatSubparserInheritedValueFlags exercising real `_parser` build (not the hand-rolled replica) so it catches future drift. - Parametrized before-chat / after-chat cases for `-t`, `--toolsets`, `-m`, `--model`, `--provider`. - Negative case: passing none of the flags leaves attrs at the top-level parser's `None` default (SUPPRESS does not remove existing attrs). - Combined case: all three value flags before `chat` simultaneously. - store_true cases for `--tui` / `--dev`. - Contract test asserting every shared-`dest` flag on chat uses SUPPRESS. Fixes #28780.
* fix(agent): persist truthful tool effect dispositions * fix(agent): preserve successful siblings during orphan recovery * fix(agent): narrow effect dispositions to none and unknown
The set-matrix step wrote the npm workspace query result directly to $GITHUB_OUTPUT. If discovery ever produced [], the matrix would expand to zero check jobs, leaving the reusable workflow green without running any JS/TS checks. Now the step validates the result is a non-empty array before emitting it, and exits 1 with a GitHub annotation if it's empty or jq failed.
…lassifier classifyNativeBinary only checked big-endian Mach-O/Fat magic bytes (feedfacf, feedface, cafebabe). Real Darwin .node files from node-pty prebuilds are stored little-endian on disk (cffaedfe = MH_CIGAM_64), so every Darwin prebuild classified as null, and validateStagedBinaries threw a platform mismatch on macOS — breaking npm run check for every macOS contributor. CI didn't catch it because runners are Linux (ELF path was correct) and tests only planted big-endian fake headers. Add recognition for all six Mach-O/Fat byte orderings: - MH_CIGAM (cefaedfe) — LE 32-bit - MH_CIGAM_64 (cffaedfe) — LE 64-bit [the one real prebuilds use] - FAT_CIGAM (bebafeca) — LE universal Update makeFakeNode to write LE CIGAM_64 bytes for the darwin fixture (matching real on-disk format) and add regression tests for all new magic forms.
…in-bash fix(windows): survive broken Git Bash login shells
Prevent credential forwarding across catalog redirects, retain explicit opt-in semantics for paid media backends, fail closed on invalid provider configuration, avoid mixed-catalog and output-limit assumptions, and reserve native STT provider names.
Forward explicit catalog refreshes and make the TTS availability gate follow the configured provider instead of unrelated credentials.
alelpoan's fix (emit hermes:zoom:changed after restore) is correct, but the bug's root is duplication: setAndPersistZoomLevel and restorePersistedZoomLevel each independently did setZoomLevel + send, and restore forgot the send. Collapse both (and the lifecycle re-assert) into a single applyZoomLevel() helper in zoom.ts that always applies-then-notifies — the regression can't recur by omitting a send. Replace the source-grep test (which broke on main: the sibling source-assertion pet test it copied was refactored to a behavioral one, dropping the fs/path imports it relied on) with behavioral coverage of the funnel, matching zoom.ts's "unit-testable without booting a BrowserWindow" convention. Co-authored-by: alelpoan <alelpoan@proton.me>
…tore-sync fix(desktop): sync UI Scale control after zoom restore (supersedes #63842)
Skipped jobs (conclusion == 'skipped') have null/zero-duration timestamps that polluted every downstream computation: they counted as 'unchanged' (0 vs 0) in faster/slower tallies, showed meaningless '0.0s (0%)' deltas in the job table, rendered phantom gantt bars, and inflated wall/compute totals. Add is_skipped() helper and apply it consistently: - compute_stats: exclude from wall/compute + faster/slower/unchanged; add 'skipped' and 'bl_skipped' counts - _gantt_bars: filter from current bars, baseline bars, and axis calc - _job_table: show 'skipped' label instead of durations/deltas - _step_details: skip entirely (no meaningful step data) - _regressions: exclude from both current and baseline sides
Add wait time computation: for each job, wait_s = started_at - max(completed_at of all jobs that finished before it started). This is a timestamp heuristic (no workflow YAML dependency parse needed) that's accurate for pipeline-shaped CI where the critical path is linear at each stage. Shows up in: - Job table: new Wait + Δ Wait columns - Gantt chart: hatched bar segment before the run bar - Step details: '(wait Xs)' annotation in the summary line - Markdown summary: Total wait row with delta vs baseline - Stats: total_wait / bl_total_wait in compute_stats Also completes the skipped-jobs UI: - Skipped stat card in stats cards - Skipped row in markdown summary table Backward compatible: old cached baselines without wait_s annotate on load.
Baseline bars were positioned at their absolute timeline offset, which included the baseline run's own wait time — making duration comparisons hard since the bars were visually offset. Now each baseline bar starts at the same left position as its corresponding current job, so the two bars directly overlap for at-a-glance duration comparison. Also removed the now-unused bl_t0 / bl_max / bl_jobs_timed variables that tracked the baseline timeline position.
run_job() constructs SessionDB() synchronously with no timeout of its own, unlike the agent's run_conversation call further down, which is already bounded by HERMES_CRON_TIMEOUT. A wedged sqlite3.connect (e.g. a stale flock from a crashed sibling process) hangs this call indefinitely. That hang is invisible to every existing cron safeguard because it happens before _submit_with_guard's future exists: the finally block that discards the job ID from _running_job_ids never runs. The job stays wedged "running" — every later tick logs "already running — skipping" — until the whole gateway process is restarted. Observed in production: a cron job's worker thread was confirmed via a live py-spy thread dump to be parked inside SessionDB.__init__'s sqlite3.connect for 3+ days, silently skipping every scheduled fire in between across a gateway process that otherwise stayed healthy. Bound the SessionDB() construction with its own timeout (HERMES_CRON_SESSION_DB_TIMEOUT, default 10s), following the same bounded-thread-pool pattern already used elsewhere in this file (the delivery retry path, and the agent inactivity watchdog just below). On timeout, log at ERROR and proceed with session_db=None instead of degrading silently to debug level, since an actual hang here is a new condition worth surfacing. Adds tests/cron/test_sessiondb_init_hang.py, including an end-to-end regression proving the dispatch guard is released and a subsequent tick can fire the same job again after a simulated hang.
Salvage of #63935. The original fix read HERMES_CRON_SESSION_DB_TIMEOUT from a bare env var, but AGENTS.md requires non-secret behavioral settings to live in config.yaml with an env var bridge only for backward compatibility. Changes: - Add cron.session_db_timeout_seconds to DEFAULT_CONFIG (default 10s) - Resolution order: HERMES_CRON_SESSION_DB_TIMEOUT env override → cron.session_db_timeout_seconds in config.yaml → 10s default (mirrors the existing script_timeout_seconds pattern) - 0 = unlimited (opt-in for debugging, skips the bound) - Strengthen test: assert the warning is logged on invalid env value (caplog was taken but never asserted) - Add test: verify config.yaml resolution path works end-to-end Co-authored-by: LoicHmh <26006141+LoicHmh@users.noreply.github.com>
The _PROVIDER_PREFIXES frozenset in agent/model_metadata.py is static and does not auto-extend from ProviderProfile. Removing deepinfra and deep-infra from it broke provider:model prefix stripping for DeepInfra.
`_module_registers_tools()` reads each `tools/*.py` file and fully AST-parses it to check for a top-level `registry.register()` call. 90 files are scanned on every process start — but only 32 actually register tools. Add a cheap text prefilter: after reading the file (which we need to do anyway for AST), check that both `"registry"` and `"register"` appear in the source before calling `ast.parse`. A file with a top-level `registry.register()` call must contain both strings, so this is a perfect superset — zero false negatives. 50 of 90 files skip the AST parse entirely. The `source=` parameter is not threaded through `discover_builtin_tools`; the prefilter lives entirely inside `_module_registers_tools`, keeping the public API unchanged. Benchmark (median of 10 runs, scanning 90 files): before (read + ast.parse all): 305.9ms after (text prefilter + ast): 187.8ms speedup: 1.6x (118ms saved) Identical module set: 32 modules, same names, same order.
… (#55546) The retry loop computed safe_out from the error's available_tokens, which reflected the *previous* request. Between retries the agent appends tool results and error text, so the real input token count grows. Deriving safe_out from the stale budget meant every retry still exceeded the context ceiling by 1+ tokens, burning through the 3-attempt limit. Compute safe_out from estimate_messages_tokens_rough(messages) so the cap tracks the growing input on each retry attempt.
…ry cap The branch computed safe_out from estimate_messages_tokens_rough(messages), but the provider rejected the larger api_messages request (system prompt, injected context, tool schemas). When API-only content is large, safe_out could far exceed the provider's available_tokens. Compute safe_out from estimate_request_tokens_rough(api_messages, tools=...) and keep provider available_out as an upper bound. Do not alter context_length or trigger compression for output-cap errors. Add production-path run_conversation tests that assert the retry API call's max_tokens, including a case where a large system prompt makes messages-only estimation undercount the real request. Fixes #55546
…mpression-disabled guard Salvage of #63862. is_output_cap_error() returns False for vLLM/LM Studio error messages that contain 'prompt contains ... input tokens' (treated as input-overflow signal). But parse_available_output_tokens_from_error() CAN extract a valid available_tokens from those same messages. The compression-disabled guard only checked is_output_cap_error(), so vLLM/LM Studio users with compression off still got a terminal failure instead of the max-tokens retry. Fix: also exempt when parse_available_output_tokens_from_error() returns a value — that function determines whether the retry path can actually handle the error, so it's the right predicate for the exemption. Added test: verify vLLM-format error with compression_disabled=False still triggers the max-tokens retry path. Co-authored-by: dmabry <dmabry@users.noreply.github.com>
#63955 made Hermes survive a broken `bash -l` (Ainz's `Directory \drivers\etc does not exist`) by falling back to non-login `bash -c`. But a non-login shell never sources /etc/profile, so it never gets `…\usr\bin` on PATH — and that dir holds every coreutil the file/terminal tools shell out to (cat, mktemp, mv, wc, head, stat, chmod, mkdir, find). Result: `write_file` returned bytes_written:0 with an EMPTY error (the failure text went to a missing binary's stderr) and terminal commands exited 127. The survive-broken-login-bash fix was only half-done: it stopped crashing but silently failed every write. Derive Git Bash's bin dirs (mingw64/bin, usr/bin, bin, …) from the resolved bash.exe and prepend them to the subprocess PATH on Windows, in /etc/profile precedence order so coreutils win over same-named System32 tools (find.exe, sort.exe) inside the shell. No-op off Windows and when a login snapshot is healthy (the snapshot re-exports the full PATH inside the shell), so this only bites on the broken-login fallback path. Adds _git_bash_bin_dirs() (derivation, cached) + _prepend_git_bash_dirs() (PATH merge), plus regression tests for PortableGit/MinGit layouts and the run-env injection ordering.
…oreutils-path fix(windows): put Git Bash coreutils on PATH for the non-login fallback
Syncs 3,189 upstream Hermes commits into the fork. 33 conflicts resolved with
the "keep ClawPump customizations, fold in upstream" philosophy.
Notable resolutions:
- Electron layer .cjs->.ts refactor: re-threaded ClawPump bootstrap / self-update
fork-pin (ensureClawpumpOrigin), dashboard-token, and hardening into upstream's
new .ts modules; fixed 4 stale dashboardArgs->backendArgs refs that would have
thrown at runtime.
- auth.py / model_setup_flows.py: kept ClawPump usepod branches alongside
upstream's new copilot / zai branches.
- Restored ClawPump features the initial resolution dropped (caught by the
build/type gates): composer $pendingChatPrompt drain (the wallet/x402/mail
"use in chat" prefill), the Sparkles icon export, and the ChatSidebar tools
panel + web/src/components/ToolCall.tsx.
- PodSetupDialog: upstream @nous-research/ui Button API change (variant ->
outlined).
- Upstream-deleted, ClawPump-rebranded files (backend-ready.cjs,
use-message-stream.ts, thread.tsx, Backdrop.tsx): accepted upstream's refactor
and re-applied the "Claw Agent" strings to their new homes (backend-ready.ts,
use-message-stream/{gateway-event,index}.ts, thread/status.tsx).
- apps/desktop version -> 0.17.0 (upstream), ClawPump branding retained.
Verified before this commit: 460 ClawPump tests pass; web + desktop tsc -b and
full builds clean; Python compiles and imports.
Flagged for reviewer:
- scripts/install.ps1 is brace-balanced but was not PowerShell-parsed locally
(no pwsh) - parse before release.
- Backdrop claw watermark not yet re-added; upstream replaced Backdrop.tsx with a
`backdrop` plugin slot - re-add as a follow-up.
- i18n "Hermes Cloud" strings left untranslated (they name the external Nous
service); rebrand later if desired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upstream sync — 3,189 commits
Merges
upstream/main(NousResearch/hermes-agent) into the ClawPump fork. 2,985 files, +392k/-56k. Real merge commit (2 parents) so the shared merge-base is preserved — merge with a merge commit, do not squash.Conflicts resolved (33)
"Keep ClawPump customizations, fold in upstream." Resolved via a fan-out of one reviewer per file, then hand-verified. Highlights:
.cjs → .tsrefactor (the big structural one): upstream exploded the electron layer into ~30.tsmodules. Re-threaded ClawPump's bootstrap / self-update fork-pin (ensureClawpumpOrigin— verified present at both update entry points), dashboard-token, and hardening into the new structure. Fixed 4 staledashboardArgs → backendArgsrefs that would've thrown at runtime.auth.py,model_setup_flows.py): kept ClawPumpusepodbranches alongside upstream's newcopilot/zaibranches.backend-ready.ts,use-message-stream/*,thread/status.tsx).apps/desktopversion → 0.17.0 (upstream), ClawPump branding kept.Dropped-feature regressions the build gates caught & I fixed
The autonomous resolution silently dropped several ClawPump additions; each was caught by tsc/tests and restored:
$pendingChatPromptdrain — the wallet/x402/mail "use in chat" prefill. Re-homed intouse-composer-draft.ts.Sparklesicon export — broke the MCP page. Restored.ToolCall.tsx(whole feature spanned both;ToolCall.tsxhad been staged for deletion). Restored.PodSetupDialog— upstream@nous-research/uiButtonvariant→outlined.Verification (all green before commit)
tsc -b+ full build (renderer + electronmain.mjs)tsc -b+ buildscripts/install.ps1— brace-balanced but not PowerShell-parsed locally (nopwsh). Parse before release.Backdrop.tsxwith abackdropplugin slot; watermark not yet re-added (deferred, cosmetic).🤖 Generated with Claude Code