Repository navigation
Merge upstream NousResearch/main into fork (2026-06-29 parity sync) - #119
Conversation
…terns The MiniMax output-layer safety filter surfaces the error verbatim as `output new_sensitive (1027)` (sometimes with additional provider wrapping like 'Stream stalled mid tool-call: output new_sensitive (1027)'). When the model emits a large tool-call argument block, the upstream filter trips and the SSE stream is truncated mid-flight, producing 'stream stalled mid tool-call' errors. Until now this case was misclassified and retried 3x on the same provider, reproducing the same refusal and burning paid attempts. Adding `new_sensitive` to `_CONTENT_POLICY_BLOCKED_PATTERNS` routes it through the existing is_client_error path: skip 3x retry, activate configured fallback model immediately, surface a clear provider-safety message to the user. Refs NousResearch#32421
…sResearch#32421) When a provider's output-layer safety filter (MiniMax "output new_sensitive (1027)", Azure content_filter, etc.) kills a streaming response after deltas were already sent, interruptible_streaming_api_call swallows the raw error into a finish_reason=length partial-stream stub. The conversation loop then burned 3 continuation retries against the SAME primary — re-hitting the content-deterministic filter every time — and gave up with "Response remained truncated after 3 continuation attempts", never consulting fallback_providers. Builds on @595650661's classifier change (cherry-picked) so error_classifier recognizes the filter; then: - chat_completion_helpers: run the swallowed error through error_classifier at the stub-creation point and stamp _content_filter_terminated on the stub (single source of truth — no parallel pattern list). - conversation_loop: read the tag and activate the fallback chain BEFORE burning any continuation retries; roll partial content back to the last clean turn and re-issue against the new provider (restart_with_rebuilt_messages). Plain network stalls are unaffected (only content_policy_blocked is tagged). Credits NousResearch#32479 (@sweetcornna) and NousResearch#33845 (@Tranquil-Flow) which fixed the same issue via the stub-tag and loop-escalation approaches respectively. Live E2E confirmed: before, _try_activate_fallback called 0x; after, fallback fires on the first stub and the fallback provider completes the turn.
…#32791) (NousResearch#54063) * docs(discord): document bot-to-bot comms as unsupported (NousResearch#32791) Multi-profile bot-to-bot conversation is not a supported topology. DISCORD_ALLOW_BOTS=none (the default) blocks all bot-originated messages; setting mentions/all across multiple Hermes profiles to make them reply to each other ack-loops because Discord's reply auto-mention satisfies the mention gate every turn. Document the safe default and the loop hazard so operators don't wire it up. * docs(discord): infographic for bot-to-bot unsupported stance (NousResearch#32791)
…ousResearch#33801) (NousResearch#54061) Secret redaction is display/output-scoped on main — write_file writes content verbatim, terminal/execute_code redact only output not the command/source. The real bug is in displayed tool OUTPUT (read_file, terminal, execute_code): _DB_CONNSTR_RE's password group [^@]+ was greedy across newlines, so on a multi-line block it scanned past the DSN line to the next stray '@' (a Python @decorator), replacing every intervening character — including line breaks — with ***. That dropped lines and concatenated the next line onto the f-string line, making read_file output look corrupted (the file on disk was always correct). Reported in NousResearch#33801. Fix: - Forbid whitespace in the userinfo/password groups ([^:\s]+ / [^@\s]+) so the match can never span a line break. A real DSN password never contains whitespace. This alone kills the catastrophic line-dropping. - Under code_file=True, preserve a password group that is a pure {...} brace expression — f"postgresql://{user}:{pass}@{host}" is an f-string template, not a live credential. Literal passwords are still masked. - Pass code_file=True at the terminal and execute_code output redaction call sites (file_tools already did) so code-execution output isn't corrupted by ENV/JSON/template false positives. Real prefixes, auth headers, JWTs, and private keys are still redacted. Verified E2E against the reporter's exact pydantic-settings module: file written verbatim, read_file shows the DSN f-string + @model_validator intact with zero *** corruption, while a literal postgresql://admin:pw@host DSN and a real sk- key are still masked. Reported-by: koishi70 Reported-by: pfrenssen
…Research#35205) (NousResearch#54076) * fix(telegram): clear send_path_degraded on successful reconnect _send_path_degraded was cleared only in _verify_polling_after_reconnect, 60s after reconnect and only if scheduled. A clean start_polling() reconnect left the flag stuck True, short-circuiting send() and blocking all outbound messages until the deferred probe ran (or forever if it never did). Clear the flag the moment start_polling() succeeds — that is the recovery signal. The deferred probe remains a defensive re-check that re-enters the reconnect ladder (re-setting the flag) if it detects a silent wedge. Fixes NousResearch#35205. * docs: add infographic for NousResearch#35205 telegram send-path fix
…ousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error #301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
…usResearch#34500) A custom_providers config that names the model under model.name (or model.model) resolved to an empty model, so the API request went out with model= — HTTP 400 from OpenAI-compatible backends. Display paths (hermes status/dump) already read model.name and showed the model, making the failure silent. The model id was read via 'default or model' at ~14 independent sites (cli, gateway, cron, curator, oneshot, fallback, profiles, ...), none of which honored 'name'. Rather than patch every site, canonicalize at the single load/save chokepoint: _normalize_root_model_keys() now promotes model.model/model.name -> model.default (precedence default > model > name) and drops the stale alias, so every reader — present and future — sees a populated default and config.yaml is migrated canonical on next save. The gateway, which bypasses load_config(), replays the same normalization in _load_gateway_config(). Co-authored-by: Bartok9 <danielrpike9@gmail.com> Credit: root-cause analysis and fix direction from @Bartok9 (NousResearch#34502, first) and @v86861062 (NousResearch#34527).
…cognized host (NousResearch#35166) On apt releases newer than the bundled Playwright recognizes (Ubuntu 26.04, Debian 14, and future distros), 'npx playwright install --with-deps chromium' hangs uninterruptibly at 'Installing Playwright Chromium with system dependencies' because Playwright's resolver maps the host to a platform with no download build (NousResearch#35166). Wrap every installer Playwright call in run_playwright_install(), which tries the native install first and, only if it fails or times out, retries once with PLAYWRIGHT_HOST_PLATFORM_OVERRIDE pinned to the newest known build (ubuntu24.04-<arch>). This is the escape hatch Playwright's maintainers bless for unrecognized platforms (microsoft/playwright#33434). Try-native-first (not a hardcoded distro/version table) is deliberate: - Self-correcting — when Playwright already supports the host (e.g. Ubuntu 26.04 on Playwright >=1.61) the first attempt succeeds and the override is never applied, so we never force a mismatched-glibc build onto a release Playwright handles correctly (microsoft/playwright#35114). - Zero-maintenance — new distro releases work the moment Playwright adds them. - Covers Debian 14+ and future releases, not just Ubuntu 26.04. An operator-set PLAYWRIGHT_HOST_PLATFORM_OVERRIDE is always respected (applied to the first attempt; retry skipped). Non-x64/arm64 arches have no fallback build and skip the retry. Refs NousResearch#35166
…p step interruptible Follow-up on NousResearch#54032 for NousResearch#35166: - Gate the PLAYWRIGHT_HOST_PLATFORM_OVERRIDE retry on the host being an apt release newer than Playwright recognizes (Ubuntu >24.04 / Debian >13) via playwright_host_unrecognized(), instead of retrying on ANY install failure. A network/disk/permission failure on a supported host now surfaces unchanged rather than getting a mismatched-glibc build forced onto it. - detect_os() now captures DISTRO_VERSION from os-release. - Fold in the interruptibility fix (was PR NousResearch#35304, self-closed): wrap the download in 'timeout --foreground -k 10' (probed, with plain-timeout fallback) so a terminal Ctrl+C reaches the child and a wedged download is force-killed after the deadline. - Add behavioral tests that source the helpers and assert the retry fires only on Ubuntu 26.04 / Debian 14, not on supported hosts, non-apt distros, native-success, operator-pinned override, or unsupported arch.
When an LLM API call returns HTTP 4xx with an empty parsed SDK `body` ({}),
`_summarize_api_error` fell through to a bare `str(error)`, so users saw only
"HTTP 400" with no provider detail (reported on Windows in NousResearch#36109). The SDK
leaves `body` empty in this case, but the httpx `response` still carries the
payload in `.text`.
- run_agent.py `_summarize_api_error`: when `body` is empty, fall back to
`response.text` — parse a JSON `error.message`/`message` when present, else
surface the raw (truncated) body. Platform-agnostic diagnostics.
- hermes_cli/oneshot.py: `hermes -z` now runs via `run_conversation` and returns
exit code 2 when the run is failed/partial with no usable final response, so
scripts can detect LLM failures (still 0 when a response — incl. an error
summary as output — is produced).
Tests: new tests/run_agent/test_summarize_api_error.py (empty-body JSON + raw
text, RED/GREEN verified) + oneshot exit-code/`run_conversation` wiring tests.
NOTE: NousResearch#36109's original root cause (Windows "all providers return empty 400")
is not reproducible on current main (heavy provider-transport churn since
v0.15.1). This change does not claim to fix that root cause — it makes any
empty-body API error LEGIBLE so a future occurrence shows the real provider
message instead of a bare HTTP 400. Relates to NousResearch#36109 (does not close it).
expand_whatsapp_aliases hardcoded get_hermes_home()/whatsapp/session, but
the adapter writes lid-mapping files via get_hermes_dir("platforms/whatsapp/
session", "whatsapp/session"). On installs without the legacy directory the
two paths diverge, so the resolver finds no mappings and returns the bare LID,
which misses the allowlist and silently drops the message. Resolve through the
same helper so both sides stay in lockstep on new and legacy layouts.
Add an _is_user_authorized E2E for the platforms/whatsapp/session layout on top of fesalfayed's resolver fix (NousResearch#36665) — guards the actual silently-dropped-LID-sender path from NousResearch#36664.
…chats (NousResearch#39293) Extend the gateway noisy-status filter beyond Telegram so internal compression lifecycle messages stay in logs instead of spamming Discord, Slack, and other messaging channels.
Force API-server error text through the existing secret redactor before returning OpenAI-compatible errors, response fallback text, response snapshots, and run failure events. This prevents credential-shaped provider failure text from crossing the API-server boundary while preserving debuggable sanitized messages.
Follow-up to the salvaged NousResearch#37733 fix. The contributor centralized redaction at _openai_error and the chat/responses failure paths, which covers the OpenAI-compatible envelopes transitively. Two sibling classes crossed the same authenticated HTTP boundary unredacted: - 8x cron-management endpoints returning {"error": str(e)} on 500 - the session-chat SSE error event ({"message": str(exc)}) Route both through the same _redact_api_error_text(force=True) helper. Add AUTHOR_MAP entry for coygeek and a TestRedactApiErrorText guard covering mask/force/limit/passthrough behavior.
When tools.environments.local can't be imported (partial install, import-time error), _is_hermes_provider_credential() returned False — fail-open. A skill could then register a Hermes provider credential (ANTHROPIC_API_KEY, etc.) as env passthrough; _scrub_child_env lets passthrough vars bypass the secret-substring net (rule 1), so the operator's real key would land in the execute_code child. Reopens the GHSA-rhgp-j443-p4rf bypass. Fail closed instead: on import failure, treat the name as a protected provider credential and refuse passthrough. Regression test exercises the full register -> scrub path under a simulated import failure. Co-authored-by: Hermes Agent <noreply@nousresearch.com>
Long-lived helpers spawned indirectly by tool calls (adb, platform bridges) were left in the service cgroup after the gateway's main process exited. When the kernel rejected the deferred cgroup-wide kill with EINVAL, systemd blocked Restart=always for 6+ minutes, taking down all platforms and cron windows (NousResearch#37454). Add a small ExecStopPost helper (gateway.cgroup_cleanup) that walks cgroup.procs and sends per-PID SIGKILLs — a different kernel code path than cgroup.kill, so it succeeds where the cgroup-wide write failed. KillMode=mixed is preserved so the gateway still reaps its own tool-call children before systemd intervenes (NousResearch#8202). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- read_text(encoding='utf-8') (PLW1514) - # windows-footgun: ok on signal.SIGKILL — module is Linux-only (reads /proc, /sys/fs/cgroup; runs from a systemd unit) - test lambda accepts the new encoding kwarg
Fix race condition in terminal environment snapshots that could corrupt PATH with declare -x entries. When concurrent terminal calls share the same snapshot file, the non-atomic 'export -p > snapshot.sh' write could be read mid-write by another process, causing partial/corrupted env vars to be sourced and mixed into PATH. The fix uses atomic file replacement: - Write to a temp file: export -p > snapshot.sh.tmp.303651 - Atomically replace: mv -f snapshot.sh.tmp.303651 snapshot.sh On POSIX, mv within the same filesystem is atomic, so source() will either see the old complete snapshot or the new complete one, never a partial/truncated file. Fixes NousResearch#38249
…lure path The atomic mv approach (kyssta-exe's commit) narrows but does not close the NousResearch#38249 race: the temp name used $$ (parent shell PID), which is identical across &-launched concurrent subshells. Two concurrent writers pick the same temp file, clobber each other mid-write, and mv then publishes a torn snapshot — a reader sourcing it absorbs declare-x/export fragments into PATH. - Use $BASHPID (actual per-subshell PID) so concurrent writers never collide. - Chain mv on export success (&&) and rm the temp on failure so a partial dump never replaces a good snapshot; apply the same to the init_session bootstrap. - shlex-quote the static temp-path portion (Windows/spaces), $BASHPID outside. - LocalEnvironment.cleanup sweeps orphaned snap.tmp.* temps. - Regression tests: string-shape + a behavioral concurrent writers/readers test that proves the snapshot never tears (would still tear with $$).
…teway-drain-microtask fix(tui): defer buffered gateway events to stop dashboard chat #301 (NousResearch#36658)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The reset-had-activity tests set total_tokens (dead state) to simulate activity; production records activity via last_prompt_tokens. Update the fixtures to match the field the fix and runtime actually use.
…ersal Session IDs can originate from untrusted input (e.g. the X-Hermes-Session-Id API header) and are interpolated raw into on-disk artifact filenames under ~/.hermes/sessions/. A traversal-shaped ID (../../../../etc/pwned) would let a caller write the session snapshot or request dump outside the sessions directory. _safe_session_filename_component() collapses every non [A-Za-z0-9_-] character to _, caps the length, and appends a short content hash when sanitization changed the string, always yielding a single traversal-free path segment. Closes NousResearch#5958.
…undary Defense-in-depth on top of _safe_session_filename_component (NousResearch#5958): Sink (makes the bad write impossible regardless of entry point): - run_agent._save_session_log: sanitize session_id before building the session_{sid}.json snapshot path. - agent_runtime_helpers.dump_api_request_debug: sanitize before building the request_dump_{sid}_{ts}.json path. Boundary (clean 400 instead of a silently-hashed filename): - api_server rejects path-traversal-shaped X-Hermes-Session-Id on the session-continuation path and the explicit /api/sessions create path, reusing gateway.session._is_path_unsafe (mirrors the native gateway's entry-boundary guard). Also enforces the session-header length cap on the continuation path. Tests: traversal session_id stays contained at the write site; sanitizer always yields a traversal-free segment; the API header rejects ../, absolute, and Windows-traversal IDs with 400.
Widen NousResearch#5961's _format_untrusted_prompt_value coverage to the Matrix room display name (**Matrix Room:**), a sibling attacker-controllable field the original fix missed. chat_name is user-settable, so an injected room name could render as literal markdown in the system prompt. Adds a regression test.
NousResearch#54834) The register path builds each profile-gateway slot in a sibling staging dir under /run/service (the scandir s6-svscan watches), then atomically renames it to the live gateway-<profile> name. The staging dir was named gateway-<profile>.tmp — a NON-dotfile — so a concurrent `s6-svscanctl -a` rescan (fired by the cont-init reconciler registering gateway-default, or by a sibling register) would supervise the half-built slot the moment it had a valid type/run: s6-supervise spawns AS ROOT and mkdirs supervise/ root-owned 0700, then the in-flight _seed_supervise_skeleton early-returns on the now-existing supervise/ and the next `mkdir supervise/event` hits PermissionError. That is the arm64-only CI flake on test_s6_unregister_removes_service_dir_in_live_container (PermissionError: /run/service/gateway-phase3test.tmp/supervise/event) — arm64-only because the native-arm runner's wider scheduling jitter lets the rescan land inside the ~ms seed window; amd64 ran 30/30 clean. Fix: dot-prefix the staging dir (.gateway-<profile>.tmp) in both register paths (S6ServiceManager.register_profile_gateway and container_boot._register_service). s6-svscan skips any scandir entry whose name begins with '.', so the half-built slot can never be supervised mid-build. The atomic rename to the dotless live name is unchanged. Verified on a real s6 image (amd64): a non-dotted staging dir is picked up by an svscanctl -a rescan (SUPERVISED owner=root) while a dot-prefixed one is ignored (NOT-SUPERVISED). Added a docker-harness regression test that asserts both, plus a unit test that the staging dir is dot-prefixed.
NVIDIA integrate.api.nvidia.com models such as minimaxai/minimax-m3 can return HTTP 200 with empty choices when max_tokens is omitted. Keep the output cap on auxiliary chat-completions routes, matching the main NVIDIA provider profile behavior.
Let users click the status bar context indicator to see how tokens are split across system prompt, tools, rules, skills, MCP, and conversation. Co-authored-by: Cursor <cursoragent@cursor.com>
…ontext-usage-popover feat(desktop): add context usage breakdown popover
…sResearch#7779) (NousResearch#54862) A manually-installed venv inside the cloned repo can be destroyed by the agent running a relative-path command against its own checkout (rm -rf venv, uv venv venv, etc.), silently wiping the running runtime mid-session. Moving the canonical manual-install venv to ~/.hermes/venvs/hermes-dev means no relative path from the agent's workspace resolves to its own runtime, making the bug class impossible without any command-detection code. Closes the root cause of NousResearch#7779. The managed install.sh layout is unchanged.
…ousResearch#54843) * feat(web_extract): truncate-and-store instead of LLM summarization web_extract no longer runs an auxiliary LLM over scraped pages. The extract backends (Firecrawl/Tavily/Exa/Parallel) already return clean, boilerplate- stripped markdown, so we return it directly: pages within a char budget (default 15000, web.extract_char_limit) come back whole; larger pages get a head+tail window plus an explicit footer giving the stored full-text path and the read_file call to page through the omitted middle. The full clean text is written to cache/web (mounted read-only into remote backends like the other cache dirs), so nothing is lost. Inline base64 images are converted to [IMAGE: alt] placeholders (token bombs dropped) while real http(s) image URLs are preserved as links so the agent can still web_extract/vision_analyze them. Removes process_content_with_llm + the chunked summarizer + check_auxiliary_model + _resolve_web_extract_auxiliary. context_references._default_url_fetcher is updated to the truncate path and its stale data.documents shape read is fixed to results (it was silently returning empty). Live before/after eval (firecrawl, 4 URLs): 11.7x faster overall (176.6s -> 15.1s); 10-60x on large pages. Quality identical; findability 4/4 (answer recoverable from stored full text on every truncated page). web_search is unchanged. No own scraper added; no changes to web_search. * fix(web_extract): add char_limit to execute_code web_extract stub The new web_extract char_limit param must appear in the code_execution_tool _TOOL_STUBS signature (and doc line) or test_stubs_cover_all_schema_params fails — the stub schema must cover every real schema param.
Subagent session pop-outs (`watch=1`) spectate a run driven elsewhere, so editing/steering the transcript from there makes no sense. Gate the composer and the user-bubble mutations on `isWatchWindow()`: - hide the composer (folds into `showChatBar`) - user prompts become a read-only button that toggles the 2-line clamp so long prompts stay fully readable, instead of opening the edit composer - drop the stop/restore actions and the checkpoint branch-picker Keyed off the narrow `isWatchWindow()` (not `isSecondaryWindow()`), so the new-session and cmd-click pop-outs are unaffected.
…atch-readonly feat(desktop): read-only spectator transcript for subagent watch windows
|
Too many files changed for review. ( |
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
312 |
unresolved-attribute |
271 |
invalid-argument-type |
191 |
invalid-assignment |
47 |
not-subscriptable |
38 |
unsupported-operator |
33 |
invalid-method-override |
25 |
invalid-return-type |
10 |
unresolved-reference |
6 |
call-non-callable |
5 |
no-matching-overload |
5 |
unused-type-ignore-comment |
3 |
not-iterable |
3 |
invalid-parameter-default |
1 |
First entries
tests/tools/test_file_tools_tilde_profile.py:21: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/relay/test_relay_passthrough.py:44: [invalid-argument-type] invalid-argument-type: Argument to `RelayAdapter.__init__` is incorrect: Expected `RelayTransport | None`, found `StubConnector`
tools/computer_use/doctor.py:114: [unresolved-attribute] unresolved-attribute: Attribute `readline` is not defined on `None` in union `IO[Any] | None`
tests/hermes_cli/test_projects_cli.py:7: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/honcho_plugin/test_oauth_flow.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
tests/test_desktop_electron_pin.py:31: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
plugins/web/xai/provider.py:186: [unresolved-attribute] unresolved-attribute: Attribute `strip` is not defined on `None` in union `Any | None | Literal["grok-build-0.1"]`
tests/cli/test_resume_display.py:717: [not-subscriptable] not-subscriptable: Cannot subscript object of type `float` with no `__getitem__` method
tests/agent/test_non_stream_stale_timeout.py:36: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `((str, dict[Unknown, Unknown], /) -> None) | None`, found `str | bool`
tests/gateway/test_telegram_username_chat_id.py:120: [unresolved-attribute] unresolved-attribute: Unresolved attribute `InlineKeyboardMarkup` on type `ModuleType`
tests/tools/test_computer_use.py:1889: [invalid-parameter-default] invalid-parameter-default: Default value of type `None` is not assignable to annotated parameter type `Exception`
gateway/platforms/qqbot/adapter.py:281: [invalid-method-override] invalid-method-override: Invalid override of method `connect`: Definition is incompatible with `BasePlatformAdapter.connect`
tests/tools/test_send_message_tool.py:1108: [invalid-assignment] invalid-assignment: Object of type `((...) -> Awaitable[dict[Unknown, Unknown]]) | None` is not assignable to attribute `standalone_sender_fn` on type `PlatformEntry | None`
tests/cron/test_scheduler_provider.py:192: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method
tests/hermes_cli/test_managed_scope_regression.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/plugins/memory/test_memory_lazy_install.py:34: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
plugins/platforms/wecom/adapter.py:48: [unresolved-import] unresolved-import: Cannot resolve imported module `aiohttp`
tests/gateway/test_media_metadata_contract.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/relay/test_relay_interrupt.py:37: [invalid-argument-type] invalid-argument-type: Argument to `RelayAdapter.__init__` is incorrect: Expected `RelayTransport | None`, found `StubConnector`
plugins/platforms/feishu/adapter.py:1358: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi`
tests/gateway/relay/test_descriptor.py:33: [invalid-assignment] invalid-assignment: Property `max_message_length` defined in `CapabilityDescriptor` is read-only
tests/hermes_cli/test_billing_cli.py:13: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `int | float`, found `str | bool`
tests/hermes_cli/test_setup_noninteractive.py:122: [not-subscriptable] not-subscriptable: Cannot subscript object of type `float` with no `__getitem__` method
tests/gateway/test_telegram_closewait_limits_31599.py:33: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
... and 925 more
✅ Fixed issues (336):
| Rule | Count |
|---|---|
unresolved-attribute |
117 |
unresolved-import |
77 |
invalid-argument-type |
61 |
invalid-method-override |
20 |
invalid-assignment |
19 |
unsupported-operator |
16 |
invalid-return-type |
8 |
no-matching-overload |
7 |
call-non-callable |
3 |
unresolved-reference |
2 |
unknown-argument |
2 |
not-subscriptable |
1 |
invalid-parameter-default |
1 |
unused-type-ignore-comment |
1 |
unresolved-global |
1 |
First entries
gateway/run.py:16102: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `str | None`, found `Any | str | list[dict[str, Any]]`
gateway/platforms/feishu.py:1368: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi.core`
gateway/platforms/whatsapp.py:347: [invalid-argument-type] invalid-argument-type: Argument to constructor `Path.__new__` is incorrect: Expected `str | PathLike[str]`, found `str | None`
gateway/platforms/feishu.py:3969: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi.api.contact.v3`
tests/tools/test_stage2_hook_user_flag_guard.py:31: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
gateway/platforms/slack.py:91: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_bolt.async_app`
gateway/platforms/matrix.py:1247: [unresolved-import] unresolved-import: Cannot resolve imported module `mautrix.crypto.store.asyncpg`
gateway/platforms/sms.py:292: [unresolved-reference] unresolved-reference: Name `aiohttp` used when not defined
gateway/run.py:8509: [invalid-argument-type] invalid-argument-type: Argument to function `get_disabled_skill_names` is incorrect: Expected `str | None`, found `Literal["local", "telegram", "discord", "whatsapp", "whatsapp_cloud", ... omitted 18 literals] | (set[Unknown] & ~AlwaysFalsy)`
tests/skills/test_google_oauth_setup.py:365: [unresolved-import] unresolved-import: Cannot resolve imported module `googleapiclient`
gateway/platforms/telegram.py:4060: [unresolved-attribute] unresolved-attribute: Attribute `HTML` is not defined on `None` in union `Unknown | None`
gateway/platforms/dingtalk.py:921: [unresolved-attribute] unresolved-attribute: Attribute `TimeoutException` is not defined on `None` in union `Unknown | None`
tests/agent/test_gemini_cloudcode.py:334: [unresolved-attribute] unresolved-attribute: Attribute `refresh_token` is not defined on `None` in union `GoogleCredentials | None`
tools/delegate_tool.py:2419: [invalid-argument-type] invalid-argument-type: Argument to function `_run_single_child` is incorrect: Expected `str`, found `Unknown | str | None | list[str]`
gateway/slash_commands.py:1268: [invalid-argument-type] invalid-argument-type: Argument to function `list_authenticated_providers` is incorrect: Expected `dict[Unknown, Unknown]`, found `None | Unknown`
gateway/platforms/feishu_comment.py:1089: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `list[dict[str, Any]]`, found `(list[dict[str, Any]] & ~AlwaysFalsy) | None`
gateway/platforms/feishu.py:2993: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi.api.im.v1`
tools/approval.py:1497: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str` in union `str | Unknown`
gateway/run.py:16102: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `str`, found `Any | str | list[dict[str, Any]]`
gateway/platforms/matrix.py:1248: [unresolved-import] unresolved-import: Cannot resolve imported module `mautrix.util.async_db`
gateway/platforms/dingtalk.py:78: [unresolved-import] unresolved-import: Cannot resolve imported module `alibabacloud_tea_util`
gateway/slash_commands.py:1859: [invalid-argument-type] invalid-argument-type: Argument to function `_home_target_env_var` is incorrect: Expected `str`, found `Literal["local", "telegram", "discord", "whatsapp", "whatsapp_cloud", ... omitted 18 literals] | set[Unknown]`
tests/hermes_cli/test_aux_config.py:54: [unresolved-attribute] unresolved-attribute: Attribute `keys` is not defined on `str`, `list[Unknown]`, `list[str]`, `None`, `int` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 32 union elements`
gateway/platforms/feishu.py:1286: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi.ws.client`
tools/delegate_tool.py:2433: [invalid-argument-type] invalid-argument-type: Argument to function `dispatch_async_delegation` is incorrect: Expected `list[str] | None`, found `(Any & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | list[str] | None`
... and 311 more
Unchanged: 5443 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
2449339 to
23d6c3c
Compare
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
Bounded upstream catch-up: 1734 commits behind origin/main (frozen target 929dd9c). 36 conflicts resolved (84 hunks). The fleet runs fork/main, so every fork-only fleet-critical system was preserved; upstream's 1546 inbound commits were absorbed. ARCHITECTURAL PORTS / SEMANTIC RECONCILIATIONS (the genuine merges, not picks): - plugins/memory/mem0/__init__.py: TWO incompatible self-hosted mem0 implementations. Took the fork's 1653-line self-contained client (_DirectRestMem0Client + Wave-2 hybrid retrieval + dedup ladder + temporal boost + forget/restore + user-id pinning) wholesale; upstream's thin delegating __init__ + _backend.py/_oss_providers.py/_setup.py (and their tests test_mem0_backend/_setup/_providers/_v3) are a DIFFERENT architecture the fork supersedes — removed as orphans. test_mem0_v2.py (UD) accept-deleted (coverage lives in fork's test_mem0_remember/_selfhost/_destructive). Rationale recorded in docs/sync/review/mem0-resolution-decision.md. - gateway/run.py hygiene compaction: merged BOTH data-loss guards — fork's Issue-8 ABORT guard (don't rewrite the user/asst-only subset on summarizer abort) AND upstream's NousResearch#21301 no-rotation guard (don't DELETE the transcript when the hygiene agent has no session_db). Rewrite now requires (not aborted) AND (rotated OR in-place). Plus fork's F2 restart-loop breaker page (_notify_restart_loop_suspended) on upstream's async _finalize_shutdown_agents; both sides' os.environ[HERMES_SESSION_KEY] removal kept. - agent/context_compressor.py: fork's per-model threshold re-resolution (compaction-thrash fix) threaded BEFORE upstream's max_tokens + _compute_threshold_tokens; constructor takes both fork (skew_floor, per_model_threshold, codex_gpt55_autoraise, calibration_hard_frac) and upstream (max_tokens) params; fork's should_defer removal kept. - agent/turn_context.py: interleaved upstream's _compression_made_progress anti-infinite-loop guard + history rebuild + retry-state resets WITH fork's P2 calibrated re-check (note_rough_sent + should_compress_calibrated). - agent/auxiliary_client.py: fork's route-scoped compaction resilience (timeout + max_retries=0 via .with_options) threaded through upstream's create_anthropic_message(); fork's atomic provider/model pair + upstream's MoA aggregator resolution; both new helper fns kept. - agent/chat_completion_helpers.py: fork's fallback-announce stack (_emit_fallback_announce, _primary_cooldown_seconds, error_context-aware try_activate_fallback) + upstream's rewrite_prompt_model_identity (used by agent_runtime_helpers); fork's reasoning-effort override + aux-routing re-sync; merged fork's stream tail-flush/surrogate-repair WITH upstream's interrupt-guard. - cron/scheduler.py: kept BOTH firing functions — fork's _process_one_job + run_job_now (backs `cron run --wait`) AND upstream's run_one_job (the external-provider contract: cronjob_tools, web_server, Chronos plugin). Folded upstream's correctness fixes (_is_cron_silence_response, _summarize_cron_failure_for_delivery, _confirm_adapter_delivery) into fork's _process_one_job. tick() fires via _process_one_job. - agent/agent_init.py: union compression-cfg + max_tokens constructor args; took upstream's normalize_tool_schema + NousResearch#47707 nameless-schema poison-guard. - tools/delegate_tool.py: adopted upstream's lockstep blocklist-derived strip set BUT preserved the fork's intent that code_execution is NOT blocked for subagents (they already have terminal) — only `delegation` stays composite- blocked. Kept fork's child send-origin/cron binding + upstream's stream_callback relay. - hermes_cli/kanban_db.py: union — both model_override (fork per-task model) and project_id (upstream) columns + both set_task_model / set_branch_name; reconciled the INSERT (21 cols == 21 placeholders == 21 values). MECHANICAL keep-both / take-upstream / take-fork resolutions: hermes_state.py (union schema cols last_turn_*/git_*; trigram degrade + _trigram_available flag + include_trigram param), gateway/session.py (fork had_any_turn durable signal), gateway/session_context.py (both send-origin + async_delivery_supported), hermes_cli/config.py (fork fleet-tuned restart_drain_timeout=180 + F2 breaker config + hygiene limits), hermes_cli/commands.py (union: undo/redo half-turn + prompt/compose; slack-via-hermes {credits,version,billing,moa,debug}), hermes_cli/models.py (both provider_label_oauth + plausibility gate), hermes_cli/backup.py (union excludes + external-memory-provider backup), agent/conversation_loop.py (fork error_context + upstream failover-system-message), agent/usage_pricing.py (fork date-strip/codex-normalize + upstream bedrock, placed in the right fn), toolsets.py (fork mem0_remember resident + upstream project-tools note), tui_gateway/server.py (both undo-clear + branch-seed). Test conflicts: add/add unions where both appended new tests (no name collisions); test_run_agent.py (DU) accept-deleted (fork split it into per-shard files); test_update_check.py call_count corrected to 5 empirically (merged path does fork's HEAD cache-key AND upstream's --is-shallow probe). LCM vendoring provenance re-validated (PASS). uv.lock + package-lock.json auto-merged clean. All 15 INV-1 modules import; run_agent/gateway/cli import end-to-end.
23d6c3c to
40591f1
Compare
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
…y upstream parity merge The 2026-06-29 upstream parity merge (PR #119) took upstream's toolsets.py structure, which silently dropped two fork-only toolsets: * messaging -> send_message (agent-callable; the fork's origin-routing work #71/#83 is built on it). Upstream deliberately omits an agent-callable send_message; this fork re-enables it. * moa -> mixture_of_agents (tools/mixture_of_agents_tool.py, fully deleted). Post-merge resolve_toolset('messaging')/('moa') returned [] with no error, so on deploy Apollo would silently lose both tools across cli/telegram/discord. Caught by the config-migration dry-run's post-migration toolset validation. Restores: - toolsets.py: messaging + moa entries (marked FORK-CUSTOM so a future merge preserves them) - tools/mixture_of_agents_tool.py (self-registers moa via registry.register) - agent/display.py: mixture_of_agents formatter + arg-key - model_tools.py: moa_tools legacy alias - acp_adapter/tools.py: mixture_of_agents in polished ACP tools - hermes_cli/tools_config.py: messaging+moa UI rows, moa default-off + env req - hermes_cli/config.py: OPENROUTER_API_KEY tools list (+ mixture_of_agents) - hermes_cli/tips.py: mixture_of_agents tip Adds tests/agent/test_fork_custom_toolsets.py — a HARD regression gate that fails CI if either fork toolset is dropped again (proven: 6/6 pass restored, 6/6 fail on the broken pre-restore tree). Hardened against the editable-install live-tree leak so a deleted module can't false-pass via a sibling install.
Distills the 5 CI-gate traps that gave false local greens during the 2026-06-29 upstream parity sync (PR #119): live-tree submodule leak, pinned gitleaks version + config auto-discovery, contributor-check's narrow noreply auto-skip, the setup.py supply-chain maintainer gate, and branch-protection required-context drift. Companion to the existing fullsuite-final-delta-classification.md.
Upstream parity sync —
NousResearch/main→ forkmain(2026-06-29)Brings the Kyzcreig fork to parity with frozen upstream target
929dd9c0d(2026-06-29 12:09 CT). True merge commit, 2 parents,HEAD..929dd9c0d = 0.Divergence at exec: fork was 222 ahead / 1734 behind (1546 non-merge upstream commits). Config version 29 → 32 (3 migrations — dry-run gated before any deploy, not in this PR).
Conflict resolution (36 files / 84 hunks, 0 leftover markers)
__init__.pywholesale (hybrid retrieval,_DirectRestMem0Client, dedup ladder);git rm'd upstream's orphaned_backend.py/_oss_providers.py/_setup.py+ tests. 371 mem0 tests pass. Rationale:docs/sync/review/mem0-resolution-decision.md.plugins/platforms/telegram/adapter.py— kept fork's network-retry ladder + upstream's heartbeat-stuck probe.hermes_cli/{backup,commands,config,models}+ 13 test files — keep-both / union / fork-primary per the per-hunk log.Real merge regressions caught by gates and fixed (not test-fudging)
Hygiene-limit flip (400→5000) restored · in-place compaction announce re-threaded ·
_kanban_worker_skill_availablerestored · redact git-identity allowlist ·provider_labelUnboundLocalError ·include_timestampgating · trigram FTS rebuild ·agent_init.py_wrappedNameError (LCM-critical) · over-folded cron failure framing · stale monolith imports. Plus the two final deltas this session:test_ignore_user_config_flags— stray untrackedcli-config.yamlartifact (not a merge issue), removed.test_run_agent_reasoning— fork test asserted pre-Cerebras API rejects reasoning_content on multi-turn replay (HTTP 400) NousResearch/hermes-agent#45655 preserve-alwaysreasoning_content; updated to an echo-back (Kimi) provider so it validates the merged Cerebras API rejects reasoning_content on multi-turn replay (HTTP 400) NousResearch/hermes-agent#45655 semantics (strip for non-echo-back providers).Full hermetic suite (~35,093 tests, per-file isolation)
0 merge regressions. Every failing/timeout file is byte-identical to upstream and fails for an env-specific reason (macOS lacks systemd · model-catalog HTTP 403 · restricted PATH omits
/usr/sbinlsof ·/tmp→/private/tmpsymlink · macOS zombie-reaping ·HOME=$HOMEcred pollution · anaconda rg 0.10.0 · bridgeinspect.getsourcewrapper · per-file isolation / slow timeouts). Full classification:docs/sync/review/fullsuite-final-delta-classification.md. Expected green on the Linux fleet + CI.CI-gate prep (validated locally)
scripts/release.pyAUTHOR_MAP (lEWFkRAD, chrispersico, KeyArgo, wnuuee1) — 0 unmapped..gitleaks.tomlto allowlist 24 new upstream test-fixture/doc-placeholder/public-OAuth-client-ID files. Triaged each — no real secret. Negative test confirms a real high-entropy secret in those source files is still caught (surgical regex, not blanket).Docs
docs/sync/— PRD (v0.3, 2-pass Opus review folded), PLAN, cron-sync spec, per-pass review logs, delta audits, resolution-decision records.hermes updateper host (host-1 ≠ this gateway), staged.