⚠️ Sync inkbox with main (CONFLICTS) - #29
Conversation
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
299 |
invalid-argument-type |
60 |
unresolved-import |
51 |
invalid-assignment |
43 |
invalid-parameter-default |
11 |
unsupported-operator |
4 |
unresolved-reference |
3 |
invalid-type-form |
3 |
unused-type-ignore-comment |
3 |
unknown-argument |
2 |
invalid-return-type |
1 |
no-matching-overload |
1 |
call-non-callable |
1 |
not-subscriptable |
1 |
First entries
run_agent.py:1282: [unresolved-attribute] unresolved-attribute: Object of type `Self@_flush_messages_to_session_db` has no attribute `session_id`
run_agent.py:1615: [unresolved-attribute] unresolved-attribute: Object of type `Self@interrupt` has no attribute `_execution_thread_id`
tests/tools/test_kanban_tools.py:1716: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str` in union `str | dict[str, str | dict[str, dict[str, str]] | list[Unknown]] | dict[str, str | dict[str, dict[str, str] | dict[str, str | list[str]]] | list[Unknown]] | ... omitted 3 union elements`
tests/tools/test_managed_browserbase_and_modal.py:105: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["agent.browser_provider"]` and value of type `SimpleNamespace` on object of type `dict[str, ModuleType]`
run_agent.py:698: [unresolved-attribute] unresolved-attribute: Object of type `Self@_emit_status` has no attribute `status_callback`
hermes_cli/kanban.py:91: [invalid-return-type] invalid-return-type: Return type does not match returned value: expected `dict[str, str] | None`, found `dict[str, str | Any | None]`
run_agent.py:2093: [unresolved-attribute] unresolved-attribute: Object of type `Self@close` has no attribute `_active_children_lock`
tests/cron/test_cron_profile.py:153: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> LiteralString, (key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str]` cannot be called with key of type `Literal["properties"]` on object of type `str`
gateway/run.py:12119: [unresolved-attribute] unresolved-attribute: Object of type `AIAgent` has no attribute `context_compressor`
run_agent.py:875: [unresolved-attribute] unresolved-attribute: Object of type `Self@_resolved_api_call_timeout` has no attribute `model`
tests/gateway/test_telegram_channel_posts.py:35: [unresolved-attribute] unresolved-attribute: Unresolved attribute `MessageHandler` on type `ModuleType`
tests/gateway/test_telegram_callback_auth_fail_closed.py:33: [unresolved-attribute] unresolved-attribute: Unresolved attribute `HTTPXRequest` on type `ModuleType`
run_agent.py:1353: [unresolved-attribute] unresolved-attribute: Object of type `Self@_save_trajectory` has no attribute `model`
hermes_cli/main.py:9542: [invalid-argument-type] invalid-argument-type: Argument to function `describe_profile` is incorrect: Expected `str`, found `str | Any | None`
hermes_cli/main.py:9512: [invalid-argument-type] invalid-argument-type: Argument to function `normalize_profile_name` is incorrect: Expected `str`, found `Any | None`
tests/hermes_cli/test_kanban_swarm.py:36: [unresolved-attribute] unresolved-attribute: Attribute `assignee` is not defined on `None` in union `Task | None`
agent/agent_init.py:132: [invalid-parameter-default] invalid-parameter-default: Default value of type `None` is not assignable to annotated parameter type `IterationBudget`
gateway/run.py:16532: [invalid-assignment] invalid-assignment: Object of type `def _status_callback_sync(event_type: str, message: str) -> None` is not assignable to attribute `status_callback` on type `(Any & ~None) | AIAgent`
tests/gateway/test_agent_cache.py:464: [unresolved-attribute] unresolved-attribute: Object of type `AIAgent` has no attribute `step_callback`
gateway/run.py:16531: [invalid-assignment] invalid-assignment: Object of type `(def _interim_assistant_cb(text: str, *, already_streamed: bool = False) -> None) | None` is not assignable to attribute `interim_assistant_callback` on type `(Any & ~None) | AIAgent`
agent/auxiliary_client.py:2949: [invalid-argument-type] invalid-argument-type: Argument to function `resolve_provider_client` is incorrect: Expected `str`, found `None | (Any & ~AlwaysFalsy)`
tests/cli/test_update_command.py:23: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_channel_posts.py:36: [unresolved-attribute] unresolved-attribute: Unresolved attribute `ContextTypes` on type `ModuleType`
run_agent.py:1575: [unresolved-attribute] unresolved-attribute: Object of type `Self@_save_session_log` has no attribute `session_log_file`
agent/agent_runtime_helpers.py:1530: [invalid-argument-type] invalid-argument-type: Argument to function `session_search` is incorrect: Expected `str`, found `Unknown | None`
... and 458 more
✅ Fixed issues (149):
| Rule | Count |
|---|---|
invalid-argument-type |
87 |
invalid-assignment |
22 |
unresolved-attribute |
22 |
unsupported-operator |
6 |
unresolved-reference |
5 |
not-subscriptable |
3 |
unresolved-import |
2 |
no-matching-overload |
1 |
invalid-return-type |
1 |
First entries
tests/agent/test_codex_cloudflare_headers.py:181: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["originator"]` and `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:1713: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["timeout"]` and value of type `int | float` on object of type `dict[str, str | dict[str, str]]`
tests/hermes_cli/test_web_oauth_dispatch.py:26: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
hermes_cli/gateway.py:3719: [unresolved-reference] unresolved-reference: Name `logger` used when not defined
run_agent.py:1716: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["args"]` and value of type `list[str | Unknown]` on object of type `dict[str, str]`
run_agent.py:9305: [invalid-argument-type] invalid-argument-type: Argument to bound method `ContextCompressor.update_model` is incorrect: Expected `int`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:9088: [invalid-argument-type] invalid-argument-type: Argument to function `resolve_provider_client` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (LiteralString & ~Literal[""]) | None | (str & ~AlwaysFalsy)`
cli.py:7773: [invalid-argument-type] invalid-argument-type: Argument to function `build_welcome_banner` is incorrect: Expected `int`, found `None | Unknown | int`
run_agent.py:2400: [invalid-argument-type] invalid-argument-type: Argument to `ContextCompressor.__init__` is incorrect: Expected `str`, found `None`
run_agent.py:8517: [unresolved-attribute] unresolved-attribute: Attribute `messages` is not defined on `None` in union `None | Unknown`
tests/hermes_cli/test_aux_config.py:50: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> LiteralString, (key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str]` cannot be called with key of type `Literal["max_concurrency"]` on object of type `str`
tests/run_agent/test_steer.py:193: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_tool_worker_threads` of type `set[int]`
hermes_cli/kanban_db.py:4349: [invalid-argument-type] invalid-argument-type: Argument to function `_safe_int` is incorrect: Expected `str | None`, found `int`
acp_adapter/tools.py:1117: [unresolved-reference] unresolved-reference: Name `json` used when not defined
run_agent.py:11018: [invalid-argument-type] invalid-argument-type: Argument to function `memory_tool` is incorrect: Expected `str`, found `Unknown | None`
run_agent.py:9377: [invalid-assignment] invalid-assignment: Invalid assignment to data descriptor attribute `base_url` on type `Self@_try_recover_primary_transport` with custom `__set__` method
run_agent.py:15463: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent._strip_think_blocks` is incorrect: Expected `str`, found `Any | None`
run_agent.py:11653: [invalid-argument-type] invalid-argument-type: Argument to function `memory_tool` is incorrect: Expected `str`, found `Any | None`
run_agent.py:4409: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:8838: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["error"]` and value of type `InterruptedError` on object of type `dict[str, None | list[Unknown]]`
run_agent.py:2806: [invalid-argument-type] invalid-argument-type: Argument to function `get_model_context_length` is incorrect: Expected `str`, found `str | dict[str, str] | Any | ... omitted 3 union elements`
tests/agent/test_skill_commands.py:47: [unresolved-reference] unresolved-reference: Name `pytest` used when not defined
tests/hermes_cli/test_kanban_db.py:1441: [invalid-argument-type] invalid-argument-type: Argument to function `_safe_int` is incorrect: Expected `str | None`, found `Literal[1700000000]`
run_agent.py:13614: [invalid-argument-type] invalid-argument-type: Argument to bound method `SessionDB.update_token_counts` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:15395: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to `list[dict[str, Any]]`
... and 124 more
Unchanged: 4259 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
991c1a2 to
08e5cfc
Compare
Extends NousResearch#26573 to also catch the case the original PR deliberately left out: when a tool raises an exception, the agent's tool executor wraps it in a canonical 'Error executing tool '<name>': ...' string prefix (see agent/tool_executor.py around the try/except). That prefix is unique to the wrapper and cannot legitimately appear in well-behaved tool output, so it is a safe signal that the tool blew up. Without this, the canonical 'tool raised' case still rendered as a green 'completed' row in Zed despite being a runtime failure — exactly the class of bug NousResearch#26573 set out to fix. Adds a positive test (raised-exception prefix -> failed) and a negative test (bare 'Error:' word in legit tool output stays completed) so a future contributor doesn't accidentally widen the rule to false-positive on compiler/linter diagnostics.
Follow-up to NousResearch#26543. The sessions table does not have an updated_at column (see hermes_state.py — only started_at/ended_at), so row.get('updated_at') always returned None and the str() coercion was dead code. Use datetime.now(UTC).isoformat() instead, which reflects exactly what the field means here: 'the title was refreshed at this moment'. Drop the dead coercion.
* feat(web): mobile dashboard UX polish Bottom sheets for sidebar theme/language pickers on narrow viewports with enter/exit animation and drag-to-close; inline header badges beside titles; bottom padding on the route outlet for scroll clearance; profiles loading uses a unicode braille spinner; align profile/cron card actions to the top; viewport-fit cover and supporting layout tweaks across dashboard pages. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Nix web npm hash and mobile sheet accessibility. Align fetchNpmDeps in nix/web.nix with web/package-lock.json for CI. Improve BottomPickSheet backdrop labeling, avoid aria-hidden on the dialog during exit animation, and wire theme/language sheets with listbox semantics and localized dismiss labels. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
… ops Three install.ps1 improvements pulled from the thin-installer work on bb/gui (PR NousResearch#27822) that benefit the canonical CLI install flow on main: 1. Strip UTF-8 BOM from scripts/install.ps1. The canonical 'irm <raw URL> | iex' install flow has been broken since commit 4279da4 re-introduced a UTF-8 BOM that PR NousResearch#27224 had explicitly stripped. PowerShell 5.1's 'irm' returns the response body as a string with the BOM surviving as a leading \ufeff character; 'iex' then evaluates that string and the parser chokes on the invisible character before param(), surfacing as a cascade of 'The assignment expression is not valid' errors at every param default value. File body is verified pure ASCII (no character above byte 127), so PS 5.1 with no BOM falls back to Windows-1252 decoding which is identical to ASCII for our content. Both install paths work: - 'irm ... | iex' (canonical one-liner) - 'powershell -File install.ps1' (programmatic / desktop bootstrap) 2. New -Commit and -Tag string params for reproducible pinning. Higher-precedence variants of -Branch. When set, the repository stage clones $Branch (fast partial fetch) and then 'git checkout's the exact ref. Precedence: Commit > Tag > Branch. Honoured by all three code paths: - Update path (existing valid checkout): fetch + checkout --detach <commit|tag> instead of checkout + pull. - Fresh clone: clone --branch $Branch, then post-clone 'git checkout --detach' to the requested ref. - ZIP fallback: pick archive URL for the most-specific ref (commit -> archive/<sha>.zip, tag -> archive/refs/tags/ <tag>.zip, else archive/refs/heads/<branch>.zip). Used by the Hermes desktop's first-launch bootstrap to pin the .exe to the exact commit it was built against, so the cloned Hermes Agent tree always matches what the .exe was tested with. Also enables release-bundle pinning (e.g. Microsoft Store builds pinning to a release tag) and CI reproducibility. 3. EAP=Continue wrap around the new pin-step git invocations. 'git fetch origin <commit>' writes the routine 'From <url>' info line to stderr. Under the script's global $ErrorActionPreference = 'Stop' that stderr line is wrapped as an ErrorRecord and terminates the script even though fetch+checkout actually succeed. Same EAP=Stop + native-stderr footgun we hit during the install.ps1 hardening pass in Install-Uv, Test-Python, _Run-NpmInstall. Wrap both the update-path fetch/checkout block AND the post-clone pin block in $ErrorActionPreference = 'Continue' (restored in finally). Real failures still caught by $LASTEXITCODE checks.
* feat: add /update slash command to CLI and TUI * test(cli): add Python tests for /update slash command Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): address Copilot review for /update slash command Route classic CLI /update through prompt_toolkit modal confirmation and defer relaunch to the main-thread cleanup path after app.exit(). Tighten Y/n semantics, add Python wrapper and catalog coverage tests, and assert /update stays visible in the TUI command catalog. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): address review feedback on /update command - Replace raw input() with _prompt_text_input_modal in _handle_update_command to avoid EOF/hang/keystroke-leak races with prompt_toolkit's stdin ownership - Fix confirmation logic: only proceed on recognized affirmative aliases (y/yes/1/ok); cancel on everything else including empty string, typos, and unrecognized input — matches all other [Y/n] prompts in the codebase - Route relaunch through main-thread shutdown path: set _pending_relaunch and return False from process_command so process_loop triggers app.exit(); run() then calls relaunch() after prompt_toolkit has restored terminal modes and after cleanup — safe on both POSIX (execvp) and Windows (subprocess+exit) - Fix misleading docstring in test_update_command.py: the Vitest only covers the TypeScript slash handler that emits code 42, not the Python wrapper branch that acts on it - Rewrite tests to use SimpleNamespace pattern (like test_destructive_slash_confirm) so _prompt_text_input_modal can be stubbed directly - Add Python test for _launch_tui exit-code-42 → relaunch branch in main.py Agent-Logs-Url: https://github.com/NousResearch/hermes-agent/sessions/f6da68cf-e7b1-4b7a-aed6-3d4b0f523bdb Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com> * fix(cli): polish test fixtures for /update command - Remove unused _prompt_text_input from SimpleNamespace stub - Use pytest.fail sentinel in managed-install guard test to catch unexpected modal invocations Agent-Logs-Url: https://github.com/NousResearch/hermes-agent/sessions/f6da68cf-e7b1-4b7a-aed6-3d4b0f523bdb Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com> * chore: re-trigger CI after Copilot review fixes Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com>
…1-improvements feat(install.ps1): strip BOM, add -Commit/-Tag pin params, harden git ops
Adapts the upstream baoyu-article-illustrator skill (verbatim-copied in the previous commit) to Hermes' tool ecosystem, matching the pattern used by baoyu-infographic. - Metadata: openclaw → hermes; add author, license, tags, category - Triggering: slash command + CLI flags → natural language - User config: remove EXTEND.md, first-time-setup, preferences-schema - User prompts: AskUserQuestion (batched) → clarify (one at a time) - Image gen: baoyu-imagine → image_generate (describe refs in prompt text) - Platform: drop Windows/PowerShell; Linux/macOS only - File ops: switch to write_file / read_file - Watermark: opt-in per-article instead of EXTEND.md-driven - Add PORT_NOTES.md describing the adaptation and sync procedure Style, palette, and prompt/system.md reference files are verbatim copies and are the sync points with upstream.
…ties Addresses review feedback on NousResearch#13193: 1. Reference-image flow no longer assumes write_file/read_file handle binaries. vision_analyze produces a textual description; the binary is optionally copied via terminal (cp/curl). The description is what gets embedded in prompts. 2. image_generate's URL-only return is now explicit. Step 6 downloads the returned URL to local disk via terminal (curl -sSL -o ...), then verifies non-zero size before proceeding. 3. Removed "Please use nano banana pro..." line from prompts/system.md — the backend is user-configured and not agent-selectable, so routing hints in the prompt are misleading. PORT_NOTES.md updated: prompts/system.md is no longer verbatim, and the file-ops/backend-selection rows now reflect Hermes' actual tool surface (write_file/read_file for text, terminal for binaries and URL downloads, vision_analyze for reading images).
…tforms, regen docs
Adds the contributor email mapping for Jack Yang (@0xjackyang) so future release-note generation attributes commits correctly. Salvage of NousResearch#27964 by @0xjackyang.
Pre-stages AUTHOR_MAP entries for 5 new contributors whose PRs are being salvaged in the May 2026 low-hanging-fruit batch (group 7). Lands ahead of the per-PR salvage PRs so they don't get blocked by AUTHOR_MAP CI. Contributors: - 02356abc (NousResearch#28286 — wecom WSMsgType.CLOSING) - burjorjee (NousResearch#28201 — inline-shell timeout guard) - oseftg (NousResearch#28168 — natural response ending: emoji + caret) - rudi193-cmd (NousResearch#28241 — empty credential pool entries) - sadiksaifi (NousResearch#27982 — kanban horizontal scroll) Per references/batch-pr-salvage-may14-additions.md.
The WeCom adapter's _read_events() loop only handled CLOSE, CLOSED,
and ERROR websocket message types. When the server initiates a graceful
shutdown, aiohttp returns WSMsgType.CLOSING before the connection is
fully closed. This message type was not handled, causing the receive()
call to return immediately in a tight loop while self._ws.closed
remained False. The result was 100% CPU usage on the asyncio event loop.
Add WSMsgType.CLOSING to the set of terminal message types that raise
RuntimeError("WeCom websocket closed"), allowing _listen_loop() to
enter its normal reconnect backoff path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes NousResearch#28140 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gateway.log uses a _ComponentFilter that only passes records from
loggers starting with ('gateway',). Plugin modules are loaded under
the hermes_plugins.* namespace, so all plugin log output is silently
dropped from gateway.log.
This makes plugin registration — which directly affects gateway hooks
(pre_gateway_dispatch, transform_llm_output, etc.) — invisible in
the gateway-specific log. Operators debugging gateway behavior check
gateway.log and see no plugin activity, even when plugins are working
correctly.
Add 'hermes_plugins' to the gateway component prefixes tuple so
plugin log messages appear in gateway.log.
Closes NousResearch#28138
_deliver_kanban_artifacts used a broader _IMAGE_EXTS that included .bmp, .tiff, and .svg. These three extensions are absent from the equivalent set in _deliver_media_from_response (line 10661), which intentionally routes them through send_document rather than send_multiple_images (comment near line 10522 notes that Telegram sendPhoto recompresses and rejects non-raster formats). Routing .svg (XML text), .bmp, or .tiff through the photo API causes send_multiple_images to raise on most platforms; the exception is caught and logged as a warning, silently dropping the artifact. Aligning the two sets ensures kanban deliverables with these extensions follow the same send_document path as regular agent responses. No behaviour change for .png/.jpg/.jpeg/.gif/.webp.
…revent keyboard freeze Background process non-PTY path used stdin=subprocess.PIPE unconditionally, creating an orphan pipe that was never written to and never closed. Child processes that read stdin would block indefinitely, competing with the parent's prompt_toolkit event loop for terminal ownership and causing complete keyboard lockout. Change to stdin=subprocess.DEVNULL so children get immediate EOF on stdin reads instead of blocking forever. For interactive stdin, the PTY path (which has its own independent PTY via ptyprocess.PtyProcess.spawn) should be used instead. Fixes NousResearch#17959
…earch#28317) * fix(process-registry): detach stdin from background subprocesses to prevent keyboard freeze Background process non-PTY path used stdin=subprocess.PIPE unconditionally, creating an orphan pipe that was never written to and never closed. Child processes that read stdin would block indefinitely, competing with the parent's prompt_toolkit event loop for terminal ownership and causing complete keyboard lockout. Change to stdin=subprocess.DEVNULL so children get immediate EOF on stdin reads instead of blocking forever. For interactive stdin, the PTY path (which has its own independent PTY via ptyprocess.PtyProcess.spawn) should be used instead. Fixes NousResearch#17959 * chore(release): alias stale-ID salvage commit for LifeJiggy PR NousResearch#28315 was salvaged with a wrong noreply numeric ID (192385615 vs the correct 141562589). The commit on main is correctly authored to LifeJiggy by username, but the noreply email doesn't match AUTHOR_MAP. Adds an alias so release-notes generation maps both forms to the same contributor. --------- Co-authored-by: LifeJiggy <192385615+LifeJiggy@users.noreply.github.com>
Plugin discovery exceptions in gateway startup (gateway/run.py) and CLI startup (hermes_cli/main.py) are caught and logged at DEBUG level, making them invisible at the default INFO log level. If any plugin import fails — syntax error, missing dependency, import cycle — operators get zero indication unless they bump the log level to DEBUG. This makes broken plugins appear enabled but silently non-functional. Change both locations to logger.warning() so failures are visible at production log levels. Closes NousResearch#28137
… macOS
Path.resolve() follows the /tmp -> /private/tmp symlink on macOS, so
str(path).startswith("/tmp/") is always False for temp-dir paths.
The "Accept Edits" (workspace_session) mode silently refused to
auto-approve every /tmp write on macOS, breaking the documented
behaviour and making the existing test fail on this platform.
Fix: keep the raw expanded path (pre-resolve) for the /tmp prefix
check and continue using the resolved form only for the cwd
relative_to() call where symlink resolution is correct behaviour.
Switch .hermes-kanban-columns from auto-fit CSS grid to a flex row with overflow-x: auto and a hidden scrollbar (scrollbar-width / ::-webkit- scrollbar), and pin .hermes-kanban-column to flex: 0 0 280px so columns sit side-by-side at a fixed width instead of wrapping into a 2xN grid. Page vertical scroll is unaffected: each column already caps at max-height: calc(100vh - 220px), so the container never grows tall enough to introduce its own vertical scrollbar.
…sible to LLM When a tool call requires user approval in the non-blocking gateway path, the LLM previously received a result that was indistinguishable from a failed tool call (exit_code=-1, error=message). The LLM could not tell whether the tool was pending approval, had returned empty results, or had failed silently — causing it to burn context on wrong hypotheses. Fix changes the result format to include: - status: pending_approval (clear state name) - approval_pending: True (explicit boolean for LLMs to detect) - error: cleared to empty string (removes misleading error signal) This lets the LLM reason about approval latency vs actual errors, short-circuiting the previous silent failure mode. Fixes NousResearch#14806
This reverts commit b1acf80.
… Telegram entities" This reverts commit cf814c9.
…bound messages" This reverts commit db50af9.
…vent data loss (NousResearch#27856) Pre-mark all running agent sessions as resume_pending BEFORE the drain wait begins. If the service manager kills the process during the drain (window), the durable marker is already written so the next gateway boot can recover in-flight sessions. On graceful drain completion, clear the early markers for sessions that finished successfully.
…reply loops In multi-agent shared Matrix rooms, multiple bots all participating in the same thread could trigger infinite reply loops — each bot's reply re-engaged the others because they were all in the bot-thread set. Discord has a `thread_require_mention` opt-in for this; Matrix didn't. Add `_parse_thread_require_mention(config)` (mirrors Discord's pattern). In `_resolve_message_context`, when enabled and the message is in a bot-participated thread (not a free-response room), require @mention before processing. Salvage of @justemu's 2-commit stack (NousResearch#27996). Fixes NousResearch#27995.
…e_file Adds TestGitBaselineCheck with 6 unit tests covering _check_git_baseline and the warning field in write_file result: - Git not available → None - Not in a git repo → None - Clean repo → None - Dirty repo → returns warning string with branch name - write_file result includes warning when dirty - write_file result omits warning when clean
HERMES_TUI_RESUME is an internal env var the Python wrapper exports to hand a session ID off to the Ink TUI. Because _launch_tui started from os.environ.copy(), any exported/stale value in the user's shell leaked through — so plain `hermes --tui` would try to resume a missing session and leave the UI at 'error: session not found' with no live session. Drop HERMES_TUI_RESUME from the env before conditionally re-setting it from the argparse-resolved resume_session_id. Tests cover both the drop path and the set-from-arg path. Salvage of NousResearch#28080 by @noctilust.
1. trajectory_compressor.py: yaml.safe_load() returns None on empty
files, crashing with TypeError on `if 'tokenizer' in data`. Fix by
adding `or {}` fallback. (HIGH — blocks startup with empty config)
2. 6 files with fcntl.flock(LOCK_UN) in finally blocks without
try/except: cron/scheduler.py, hermes_cli/auth.py,
agent/shell_hooks.py, tools/skill_usage.py,
tools/environments/file_sync.py, tools/memory_tool.py. If unlock
raises OSError, fd.close() is skipped and the lock is held forever.
The msvcrt branches already had try/except; the fcntl branches did
not. Fix by wrapping in try/except (OSError, IOError): pass.
3. agent/copilot_acp_client.py line 639: TOCTOU race — path.exists()
followed by path.read_text() with no try/except. If file is deleted
between the check and the read, FileNotFoundError propagates. Fix
by using try/except FileNotFoundError.
4. gateway/sticker_cache.py: non-atomic write via Path.write_text()
can leave truncated JSON on crash, causing JSONDecodeError on next
load. Fix by writing to tempfile + fsync + os.replace (atomic).
…salvage (NousResearch#28594) Adds the canonical noreply form (54813621+xxxigm@users.noreply.github.com) alongside the existing plain-email mapping so the salvage commit for @xxxigm's codex doctor PR doesn't fail AUTHOR_MAP CI.
…ousResearch#27975 `hermes doctor` printed 'codex CLI not installed (optional — ...)' as a generic info line at the bottom of the auth section, several rows below 'OpenAI Codex auth (not logged in)' and after MiniMax/Gemini auth checks. Users reading sequentially mistook it for MiniMax-related advice. Move the hint up under the Codex auth warning so it's adjacent to the row it actually pertains to. Behavior unchanged when the codex CLI is installed (success path keeps its 'codex CLI ✓' row at the bottom). Tests cover both placement and suppression cases. Salvage of @xxxigm's 3-commit stack (NousResearch#27986). Closes NousResearch#27975.
…#28626) Sweep of all CI failures on origin/main, grouped by drift source: Telegram allowlist gate (db50af9 added user-authz to _should_process_message): - Hardcoded "[Telegram]" prefix in the logger.warning so the call no longer dereferences self.name → self.platform, which test fixtures built via object.__new__ never set. - test_telegram_format / test_allowed_channels_widening fixtures stub _is_callback_user_authorized → True so the new gate doesn't reject guest-mode / allowed-channels test messages. - test_telegram_approval_buttons::test_update_prompt_callback_not_affected sets TELEGRAM_ALLOWED_USERS="*" so the fail-closed default doesn't reject the callback before it writes .update_response. Approval surface (6d495d9 renamed status, 214b953 detached stdin): - test_no_callback_returns_approval_required: status is now "pending_approval" (was "approval_required"). - test_close_stdin_allows_eof_driven_process_to_finish: switch to use_pty=True; non-PTY now uses stdin=DEVNULL. Mattermost (send() now resolves root_id via _api_get first): - test_send_with_thread_reply mocks _session.get with a thread-root response so the new resolver doesn't TypeError on a bare AsyncMock. Kanban (d8ad431 rename, f55d94a review column, _kanban_worker_skill_available): - _safe_int → _to_epoch in the two test_kanban_db tests. - Spawn-skills tests (×3) monkey-patch _kanban_worker_skill_available to True since the isolated kanban_home fixture has no devops/kanban-worker tree. - test_gateway_dispatcher_disables_corrupt_board: connect count 3 → 5 (review-column probe now also runs per tick). Aux-config severity at_or_above (a94ddd8): - test_diagnostics_endpoint_severity_filter expects warning filter to include error+critical now (was exact-match). Anthropic error handling (conversation loop extracted from run_agent): - _no_backoff_wait fixture patches BOTH run_agent.jittered_backoff AND agent.conversation_loop.jittered_backoff. The latter is the actual call site; without the second patch tests burn ~2s per retry and hit the 30s SIGALRM timeout on CI. Other test pollution / drift: - test_auto_does_not_select_copilot_from_github_token: patch agent.bedrock_adapter.has_aws_credentials → False so boto3's credential chain can't auto-pick Bedrock from developer ~/.aws. - test_setup_openclaw_migration: patch hermes_cli.gateway.get_env_value in addition to setup_mod.get_env_value — _platform_status reads through the gateway module's binding. - test_gateway_prefix: COMPONENT_PREFIXES["gateway"] now includes "hermes_plugins" too. - test_recommended_update_command_defaults_to_hermes_update: also short-circuit get_managed_update_command in case a stray ~/.hermes/.managed marker is present. - test_user_id_is_not_explicit: _parse_target_ref now returns is_explicit=False for Slack U.../W... IDs (chat.postMessage rejects them — a DM must be opened first via conversations.open).
… on failure (NousResearch#28669) Catch the PR NousResearch#28452 failure mode (orphan merge-conflict markers in hermes_cli/config.py) on the user side: after git pull succeeds, compile the files every 'hermes' invocation imports at startup. If any has a syntax error, git reset --hard back to the pre-pull SHA so the install stays bootable. User can retry once a fix lands upstream. - New _capture_head_sha() + _validate_critical_files_syntax() helpers - Wires both into _cmd_update_impl after the pull/reset succeeds - Tests cover the helpers, the rollback flow, and a production-tree invariant (CI fails if main itself has a syntax error in a critical file — catches future broken commits before users hit them)
When 'hermes update' syncs bundled skills, the summary line only shows the count of user-modified skills that were kept (e.g. '3 user-modified (kept)'), but not *which* skills. Once the update finishes, the user has no way to know which skills need triage. Append the skill names to the summary line, truncated to 5 with a '+N more' suffix for long lists: Done: 12 new, 3 updated, 7 unchanged, 3 user-modified (kept): hermes-agent, debugging-hermes-tui-commands, system-health. 25 total bundled. Closes NousResearch#28121
NousResearch#28063 fixed the macOS `/tmp`→`/private/tmp` symlink issue by checking the RAW path (pre-resolve) against startswith('/tmp/'). That works on Linux + macOS but not on Windows — Path('/tmp/foo').resolve() returns C:\\tmp\\foo and isn't the real Windows temp anyway. Replace the hardcoded '/tmp/' prefix with Path(tempfile.gettempdir()). resolve() + Path.relative_to() — same idiom as the cwd branch just below. Works correctly on Linux (/tmp), macOS (/private/var/folders/...), and Windows (%LOCALAPPDATA%\\Temp). Test rewritten to use tempfile.gettempdir() so the assertion exercises the same code path on every platform. Conflict against the just-merged NousResearch#28063 (raw_path approach) resolved by replacing the whole raw_path block — tempfile.gettempdir() is strictly better than that intermediate fix. Salvage of NousResearch#28262 by @Zyrixtrex.
…h#28680) Follow-up to NousResearch#28452. detect_stale_running() was calling _record_task_failure() on every reclaim, which ticked the consecutive_failures counter. With the default failure_limit=2, two legitimately long-running tasks (>4 h without explicit heartbeat) would auto-block via the spawn-failure circuit breaker — even though no worker actually failed. Stale reclaim is dispatcher-side absence-of-heartbeat detection, not a worker fault. Removed the _record_task_failure() call; the 'stale' event in task_events is still the audit surface, but the failure counter is now reserved for spawn_failed / timed_out / crashed (real failures). Also documents the heartbeat requirement: - KANBAN_GUIDANCE in agent/prompt_builder.py now states the rule ('call kanban_heartbeat at least once an hour for tasks running longer than 1 hour') so workers learn the contract. - kanban.md adds the stale event row to the events table and flags the heartbeat requirement in the worker lifecycle list. New regression test: test_detect_stale_does_not_tick_failure_counter locks in the new behaviour.
NousResearch#28672, NousResearch#28674, NousResearch#28676, NousResearch#28678) Five small fixes against issues filed during the post-merge salvage audit: * NousResearch#28670: `_GATEWAY_PROVIDER_ERROR_RE` false-positives on legitimate prose. Replace the regex with an anchored `_GATEWAY_PROVIDER_ERROR_SHAPE_RE` and add a length-cap heuristic to `_looks_like_gateway_provider_error`: short envelope at the start of the message → real provider error; long prose containing 'HTTP 404' → assistant answer, leave alone. * NousResearch#28672: drop the pointless 1s asyncio.sleep on Telegram thread-not-found retries. The same-thread retry is preserved (catches Telegram's occasional transient flake exercised by test_send_retries_transient_thread_not_found_before_fallback) but with no artificial delay. * NousResearch#28674: broaden `_should_retry_without_dm_topic_reply_anchor` to also fire when Bot API rejects `direct_messages_topic_id` for synthetic / resumed sends that have no reply anchor. Avoids dropping post-resume background notifications if the topic id goes stale. * NousResearch#28676: delete the dead image-document branch superseded by bd0c54d (which returns early on the same extension set). * NousResearch#28678: extend chat-scoped allowlist (`TELEGRAM_GROUP_ALLOWED_CHATS`) to also cover `chat_type == 'channel'`, so operators can authorize channel posts by chat id without falling back to per-user allowlists. Tests: - scripts/run_tests.sh tests/gateway/test_telegram_thread_fallback.py -q → 41/41 - scripts/run_tests.sh tests/cron/test_scheduler.py -q → 127/127 - broader test set: same 3 pre-existing test-pollution failures reproduce on plain main.
08e5cfc to
1ce88cd
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. |
Conflicted files (markers in tree, resolve manually): README.md gateway/platforms/webhook.py gateway/run.py hermes_cli/main.py hermes_cli/send_cmd.py run_agent.py scripts/install.cmd scripts/install.ps1 scripts/install.sh scripts/release.py tools/xai_http.py uv.lock website/docs/getting-started/installation.md
1ce88cd to
5d42a44
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. |
Upstream commit 7c622b6 ("fix(kanban): migrate task session index after columns") removed three CREATE INDEX statements from SCHEMA_SQL and re-added the corresponding indexes inside _migrate_add_optional_columns, after the columns themselves are added. The legacy-board migration was failing because CREATE TABLE IF NOT EXISTS does not add new columns to existing tables, so the SCHEMA_SQL-level CREATE INDEX ran against a table that didn't yet have the indexed column. Two prior github-actions[bot] sync PRs (#29, #33) left the inkbox branch with the migration half of the fix applied (the new indexes are created after columns) but missed deleting one line from SCHEMA_SQL — specifically `CREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id);`. That kept the bug live: the test `test_connect_migrates_legacy_db_before_optional_column_indexes` has been failing on the inkbox branch since at least PR #39, and the running gateway throws `sqlite3.OperationalError: no such column: session_id` from the kanban dispatcher every minute. Surgical: delete the orphan line. After this commit, kanban_db.py is byte-identical to upstream/main. Test passes locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upstream sync — CONFLICTS
Merging
main(mirror ofNousResearch/hermes-agent@main) intoinkboxhit conflicts. Conflict markers are committed in the tree so the diff is reviewable.To resolve: pull this branch locally, fix the marked files, and force-push.
Conflicted files
Opened by
.github/workflows/sync-upstream.yml.