Skip to content

Sync upstream hermes-agent @ 5ff11a689 (CONFLICTS)#256

Draft
hermes-upstream-sync[bot] wants to merge 802 commits into
mainfrom
upstream-sync/2026-06-22-5ff11a689
Draft

Sync upstream hermes-agent @ 5ff11a689 (CONFLICTS)#256
hermes-upstream-sync[bot] wants to merge 802 commits into
mainfrom
upstream-sync/2026-06-22-5ff11a689

Conversation

@hermes-upstream-sync

Copy link
Copy Markdown
Contributor

Conflicts detected. Resolve manually before marking ready.

Files with conflict markers:

.github/workflows/docs-site-checks.yml
.github/workflows/history-check.yml
.github/workflows/nix-lockfile-fix.yml
.github/workflows/nix.yml
.github/workflows/supply-chain-audit.yml
.github/workflows/uv-lockfile-check.yml
agent/prompt_builder.py
gateway/config.py
plugins/platforms/slack/adapter.py
tests/gateway/test_display_config.py
tests/tools/test_send_message_target_parse.py
tests/tools/test_skill_manager_tool.py
tools/send_message_tool.py
tools/skill_manager_tool.py
uv.lock

Brings in upstream commits up to 5ff11a689 (upstream/main).

Generated by ops/hermes-upstream-sync. Branch protection on main
enforces that the agent cannot self-merge — review and merge manually.

5ff11a689 feat(cli): /timestamps command + timestamps in /history (#50506)
b9b4756ab fix dashboard chat session titles
5dae502b8 Address email pairing review feedback
2455e1801 Make email pairing opt-in
74f0dd62e feat(cli): Ctrl+G submits the edited draft on save (TUI parity) (#50560)
4b09903de fix Nous auth refresh for idle agents
b5bd66eac fix(telegram): observed/replied group docs of any type are cached too
4314d451c fix(gateway): accept any inbound file type across all messaging platforms
de6b3ae37 fix(terminal): bridge docker_extra_args to TERMINAL_DOCKER_EXTRA_ARGS in CLI + gateway (#50631)
6202fdfc3 fix(container): detect dashboard role under s6-overlay v3 (#49196) (#50600)
e448b2141 feat(dashboard): interactive auth setup on no-provider non-loopback bind (#50551)
9e96e7099 feat(cli): /prompt — compose your next prompt in $EDITOR (#50509)
95d53c3bc feat(cli): /reasoning full — show complete thinking, not 10-line clamp (#50499)
b0a25980f fix(terminal): make hermes install dir reachable in subshell PATH (#50534)
4c1934dd8 docs: repoint remaining stale gateway/platforms adapter refs to plugins/platforms
0768ed3b3 docs(agents): fix stale platform adapter path in token-lock note
7130d6086 feat(providers): remove google-gemini-cli + google-antigravity OAuth providers (#50492)
5bf23ff25 fix(banner): don't advertise toolsets/skills the agent wasn't given (#50497)
8cfcbd327 fix(process): SIGKILL the whole tree on escalation, not just wait_procs survivors
8cbb34b2b chore: map tkwong co-author email for #15008 SIGKILL-escalation credit
8cecaf0b2 feat(process): escalate SIGTERM->SIGKILL on host-pid termination after grace
41fe086eb style(security-audit): add explicit encoding to read_text calls (ruff PLW1514)
f45ace931 feat(security): startup security posture audit (warn-on-load)
eb51c180e fix(docker): replace dashboard --insecure with basic-auth provider
7726ce304 fix(security): close hermes-0day MCP-persistence attack surface
9bf9a9f1f fix(swe-runner): move logging.basicConfig out of Runner __init__ into main
0a7ae28eb fix(compressor): remove logging.basicConfig from library class __init__
2b3a4f0af fix(agent): strip stale reasoning_content when falling back to a strict provider (#50480)
73340d8be chore: add buihongduc132 to AUTHOR_MAP for mem0 salvage
452a725ae fix(mem0): address PR review — restore docstrings, keep api_key required
b6d2ac176 feat(mem0): add self-hosted support via MEM0_HOST / host config
012f40c98 fix(status): cross-platform start-time fingerprint via psutil fallback
1cefc2a24 test(whatsapp): fix port-spares-client test race (listen before announce + retry connect)
0fb3b13b0 chore: add valentt to AUTHOR_MAP for #43846 salvage
615a8e651 fix(whatsapp): add missing re import + fix test import path after adapter relocation
069ab40c5 fix(whatsapp): only kill LISTENers when freeing the bridge port, never clients
77fdbbfe8 fix(whatsapp): validate bridge PID identity before killing stale pidfile entry
e44772314 fix(process-registry): re-validate PID identity before killing host processes
84e1d31e5 refactor(kanban): fold worker/orchestrator skills into injected guidance (#50473)
e5e258363 fix(desktop): relaunch on Linux after in-app update instead of hanging (#45205)
1f6994d1e chore(release): add AUTHOR_MAP entry for #45205 salvage (EtherAura)
1ec4fcf61 Merge pull request #50466 from NousResearch/bb/composer-popout-bounds
13ce81190 fix: show desktop approval fallback (#46548)
84fcbbf6a fix(security): quote HERMES_TIMEZONE in remote code execution to prevent shell injection
bef1d3e4f fix(desktop): filter undefined entries in AttachmentList to prevent refText crash on session switch (#49624)
16aeba170 fix(desktop): clamp composer peel-off under cursor
c768c4b71 fix(antigravity): move model flow to model_setup_flows + stop bare-alias hijack
37c37c9dc fix(antigravity): register google-antigravity ProviderProfile + AUTHOR_MAP
b7a912ea4 fix(antigravity): bake in public OAuth client + default project fallback
8baa4e997 feat(cli): add native Antigravity OAuth provider

kshitijk4poor and others added 30 commits June 20, 2026 10:57
…ion_search

Follow-up to the soft-archive durability fix. Reusing the rewind/undo active=0
flag for compaction-archived turns inherited the wrong search semantics: undo
rows are intentionally HIDDEN from session_search (the user took them back), but
compaction-archived turns must stay DISCOVERABLE — that is the whole point of
Teknium's "searchable / recoverable" requirement. As built, search_messages
defaulted to WHERE active=1, so after in-place compaction the pre-compaction
turns were in the FTS index but filtered out of the default search. (The earlier
"searchable" claim only held for a raw FTS query / include_inactive=True, not
the actual session_search tool.)

Empirically confirmed the gap: search 'HMAC' returned 2 hits before compaction,
1 after (only the summary's mention) — the originals were hidden.

Fix — a `compacted` flag distinct from `active`, giving a 3-way state:
- active=1, compacted=0  → live context (normal)
- active=0, compacted=1  → compaction-archived: OUT of live context, IN search
- active=0, compacted=0  → rewind/undo: OUT of live context, OUT of search

Changes:
- messages.compacted INTEGER NOT NULL DEFAULT 0 added to SCHEMA_SQL. Declarative
  _reconcile_columns adds it on existing DBs — no version bump (plain column add).
- archive_and_compact: UPDATE … SET active=0, compacted=1 (was active=0 only).
- search_messages: default WHERE active=1 → (active=1 OR compacted=1), on BOTH
  the main FTS5 path and the trigram CJK path. include_inactive=True still
  returns everything. The short-CJK LIKE fallback already returns all rows
  (no active filter) — unchanged.
- Docstrings on archive_and_compact + search_messages document the 3-way state.

Verified: after compaction, session_search default finds the archived originals
(ids 1 & 4); rewind/undo rows stay hidden by default (recoverable via
include_inactive); live context still excludes both. 322 in-place + hermes_state
tests and 46 session_search tests green; ruff clean. Mutation check: reverting
the search WHERE to active-only fails the new searchable test.

(Surfaced by the question "is search semantic or only FTS?" — answer: session
search is FTS5 keyword/BM25 only, no embeddings over the transcript; semantic
retrieval lives in the optional memory-provider layer. Tracing that confirmed
the active-only filter gap above.)
Review nit (yoniebans): the config.py comment still said compaction is
'lossy: the pre-compaction transcript is discarded, matching Claude Code /
Codex' — leftover from the original destructive design. The shipped behavior
is soft-archive: lossy for the LIVE context (what the model reloads), but the
pre-compaction turns are kept on disk (active=0, compacted=1), searchable via
session_search and recoverable. Comment now says so. Comment-only; no behavior
change.
…ion event (#49738)

Async-delegation completions (delegate_task(background=true)) and
background-process completions (terminal notify_on_complete) re-enter the
originating session as internal MessageEvents. When the session was busy,
_handle_active_session_busy_message treated them like a user TEXT message and
the default busy_input_mode='interrupt' aborted the active turn (and sent a
'Interrupting current task' ack) — the opposite of the design invariant that a
completion surfaces as a new turn only when idle.

Short-circuit internal events to return False so the base adapter queues them
silently (it already excludes internal events from debounce), cascading them as
the next turn after the current one finishes.
…idated return (#49734)

* feat(delegation): single-task delegate_task always runs in the background

The model no longer decides whether a subagent runs in the background — a
single-task delegate_task from the top-level agent is now always dispatched
async, so the parent turn returns immediately and the subagent's result
re-enters the conversation when it finishes.

- run_agent._dispatch_delegate_task (the live model path) forces
  background=True for top-level single-task calls; the schema-level
  `background` param is ignored.
- A batch (tasks with >1 item) stays synchronous (fan-out can't go async).
- A delegation from an orchestrator subagent (depth > 0) stays synchronous —
  it needs its workers' results within its own turn.
- The function-level default is unchanged, so direct Python callers/tests keep
  the historical synchronous behavior.
- On async-pool capacity rejection, single-task now falls through to a
  synchronous run instead of erroring (the child stays attached for interrupt
  propagation; detach happens only on a successful dispatch).
- Schema `background` param marked deprecated/ignored; tool description
  updated to state the always-background single-task rule.

* feat(delegation): all delegate_task fan-out runs in the background

Extend the always-background behavior to the full fan-out. A batch is now
dispatched as N independent async subagents (one handle each), instead of
running synchronously. Single task and batch both return immediately; each
subagent's result re-enters the conversation as its own message when it
finishes.

- delegate_task: when background is set, loop over ALL built children and
  dispatch each via dispatch_async_delegation; return a combined handle block
  (count + per-task delegation_ids). Children the async pool rejects (at
  capacity) run synchronously inline and are reported alongside the dispatched
  handles, so nothing is silently dropped.
- run_agent._dispatch_delegate_task + registry handler: force background for
  any top-level model delegation (single OR batch); orchestrator subagents
  (depth > 0) still run synchronously since they need workers' results within
  their own turn.
- Removed the v1 'batch async not supported' rejection.
- Tool description updated: BOTH MODES RUN IN THE BACKGROUND.
- Tests updated to assert batch fan-out dispatches each task async (verified
  E2E: 3-task batch -> 3 independent completion-queue events).

* fix(delegation): background fan-out joins and returns one consolidated block

Correct the fan-out semantics: a backgrounded batch is dispatched as ONE
async unit (one handle, one async-pool slot), not N independent dispatches.
The unit runs all children in parallel, waits on every one, and emits a
SINGLE completion event carrying the consolidated per-task results. The chat
is never blocked; when all subagents finish, their full summaries re-enter
the conversation together as one message.

- async_delegation.dispatch_async_delegation_batch + _finalize_batch: a batch
  occupies one slot; its runner returns the combined {results:[...]} dict and
  one event with the full results list is pushed to the completion queue.
- delegate_tool: extract the sync execution+aggregation into
  _execute_and_aggregate(); background dispatches it via the batch unit and
  returns one handle; on pool-capacity rejection it runs the batch inline.
- process_registry._format_async_delegation: render a consolidated multi-task
  block (TASK i/N + per-task summary) when the event carries is_batch/results.
- Tests updated; E2E verified: 3-task batch -> immediate return -> one combined
  completion block with all three summaries.
… (401/403)

When the active provider returns a 401/403 that survives its per-provider
credential-refresh attempt (revoked OAuth, blocked/expired key, or an
account pinned to a dead/staging inference endpoint), the conversation
loop now escalates to the configured fallback chain instead of dead-ending.

Before: the generic failover dispatch fired only for {rate_limit, billing};
auth/auth_permanent fell through to 'switch providers manually' advice and
never called _try_activate_fallback(). A user whose primary credential was
broken kept thrashing on the same dead credential every turn — the main
agent appeared 'stuck in fallback mode' while never actually failing over.
This also affected auxiliary tasks (compression, vision, title-gen), since
auto-resolved aux follows the main provider.

After: a persistent auth failure with a configured fallback chain switches
to the next provider (mirroring the rate-limit/billing failover path),
guarded one-shot per attempt by TurnRetryState.auth_failover_attempted.
When no fallback is configured the behavior is unchanged — it falls through
to the existing terminal handling and provider-specific troubleshooting
guidance.

Tests: test_auth_provider_failover.py — 401/403 classify as auth, the
gating condition fires only with a chain present + guard unset, the guard
blocks repeats, and non-auth (500) errors do not trigger auth failover.
…graded session

When the auxiliary summary call fails with an authentication/permission
error (HTTP 401/403), context compression now ABORTS and preserves the
session unchanged instead of rotating into a child session with a
placeholder summary.

Before: a 401 (invalid/blocked key, or a token pointed at the wrong
inference host) fell through every transient-error check to 'return
None', and because compression.abort_on_summary_failure defaults False,
compress() took the static-fallback path and rotated the session anyway
(messages N->N). The user landed on a fresh-but-broken session that kept
failing the same way — paying for a full-context API call each turn with
no useful compression.

After: _generate_summary classifies 401/403 as a non-recoverable auth
failure (_last_summary_auth_failure) and compress() aborts on it
regardless of abort_on_summary_failure. A distinct auxiliary summary_model
that 401s still retries once on the main model first (its dedicated creds
may be the only broken thing); the abort only sticks when the main model
itself auth-fails or the fallback also auth-fails. The existing
_last_compress_aborted handling in conversation_compression.py already
skips rotation and emits a warning, so no session rotation occurs.

Tests: TestAuthFailureAborts — 401/403 flagging, compress() aborts despite
flag=False, non-auth failures keep the historical fallback path, and
aux-model auth failure recovers on main without aborting.
Adds a ConvertTo-LongPath helper to install.ps1 that expands a Windows 8.3
short path (e.g. C:\Users\FIRST~1.LAS) back to its long form via
Scripting.FileSystemObject. Paths without a "~<digit>" component are returned
unchanged (no COM round-trip), and any COM failure falls back to the input.

Adds an AST-loaded unit test that exercises the helper without executing the
installer body (pass-through, null/empty, and graceful fallback).
… don't abort

On a Windows profile whose folder name contains a space (e.g. "First Last"),
Windows can expose %TEMP%/%TMP% as an 8.3 short path
(C:\Users\FIRST~1.LAS\AppData\Local\Temp). PowerShell's FileSystem provider
mishandles the "~1.ext" component when the path reaches a provider cmdlet such
as `Tee-Object -FilePath`, throwing:

  An object at the specified path C:\Users\FIRST~1.LAS does not exist.

Every Node/Electron install+build stage streams its log to %TEMP% via
Tee-Object, so they all abort with that error (browser-tools npm, Playwright,
TUI npm, and the hard-failing desktop build), while the Python/uv stages --
which never write a side log to %TEMP% through a provider cmdlet -- succeed.

Normalize %TEMP%/%TMP% to their long form once, up front, so every downstream
cmdlet and child process sees a path the provider can resolve.

Fixes #39308
…o strict providers

Per-message timestamp metadata injected by _apply_persist_user_message_override
leaks into the Chat Completions payload sent to the provider. Strict OpenAI-compatible
providers (e.g. Fireworks-backed endpoints like OpenCode Go 'glm-5.2', Mistral, Kimi)
reject this schema-foreign field with HTTP 400:

  Extra inputs are not permitted, field: 'messages[0].timestamp'

The ChatCompletionsTransport.convert_messages already strips known internal-only
fields (tool_name, _-prefixed scaffolding keys, codex_reasoning_items, etc.) — add
timestamp to that list.

Closes #47868
Add a regression test for #47868 asserting convert_messages strips the
internal per-message timestamp field, plus the identity-return path for
timestamp-free message lists. Map x7peeps for the release attribution gate.
In Docker the install tree (/opt/hermes) is read-only, so npm install for
the WhatsApp bridge fails with EACCES. Add resolve_whatsapp_bridge_dir() in
whatsapp_common.py: when the install dir is read-only, mirror the bridge
source into a writable HERMES_HOME location and use that. Both the
adapter and the 'hermes whatsapp' CLI resolve through the shared helper so
the install and runtime paths agree.

Fixes #49561
Follow-up for salvaged #49654: unit tests for resolve_whatsapp_bridge_dir()
(writable passthrough, read-only mirror, existing-mirror reuse) and the
AUTHOR_MAP entry for the contributor.
…aram (#49854)

The kanban-worker skill taught kanban_complete with three full examples but
never mentioned the artifacts=[...] parameter added in #27813 — so a worker
reading the skill had no way to learn it can ship a chart/PDF/image as a
native upload to the subscriber's chat.

Adds a 'Shipping deliverables' section covering absolute-path rules, the
inline-vs-file extension behavior, and the trap that the notifier reads the
top-level artifacts list (NOT metadata.*).
The dispatcher treated workspace_kind=worktree as metadata only and never
ran 'git worktree add', so every worktree task ran in the main repo checkout
instead of an isolated worktree — concurrent tasks silently shared one tree
and contaminated each other.

This materializes a real linked worktree at <repo>/.worktrees/<task_id> on
branch wt/<task_id> when resolve_workspace() handles a worktree task, treats a
repo-root workspace_path as shorthand for that location, persists the derived
workspace/branch back onto the task row, and — on rerun/redispatch — detects an
already-materialized linked worktree (via git-common-dir) and reuses it instead
of nesting a second .worktrees/<id> inside it.
Follow-up to the salvaged worktree-materialization fix. When a worktree
task has no explicit workspace_path, resolve the anchor from the board's
default_workdir (a git repo) and materialize <repo>/.worktrees/<id> per
task, instead of silently rooting under the dispatcher's CWD (whatever
directory launched the gateway, e.g. the Hermes checkout). If no
default_workdir is configured, raise with a clear message rather than
guessing from CWD.

Adds AUTHOR_MAP entry for the salvaged commit.
On Windows, hermes writes writer.bat (@echo off / hermes -p writer %*)
with CRLF endings instead of the POSIX writer shell script. The test
hardcoded the POSIX path and exact bytes, so it failed on Windows hosts.
Assert on stripped non-empty lines per platform, making it line-ending-
and OS-independent.
…(#49890)

doctor's npm audit hardcoded PROJECT_ROOT/scripts/whatsapp-bridge. In
read-only Docker installs the bridge deps live in the writable HERMES_HOME
mirror (#49561), so node_modules was never found there and the bridge audit
silently skipped. Resolve the dir through the shared
resolve_whatsapp_bridge_dir() helper so doctor audits where deps actually
install. Falls back to the install-tree path if the helper is unavailable.
…ng them

When the agent is busy and the user sends multiple text follow-ups, the
interrupt-mode and steer-fallback path stored them via
merge_pending_message_event(merge_text=True), which newline-joins
consecutive TEXT messages into a SINGLE pending turn — collapsing two
separate user messages into one mashed-together turn and destroying the
message boundaries the user sees (#43066 sub-bug 2).

Route that storage through _queue_or_replace_pending_event (the same FIFO
infrastructure used by busy queue-mode and /queue) so each follow-up gets
its own next-turn slot in arrival order, while still preserving
photo-burst / album merge semantics for media. Pure queue-mode already
used FIFO; this brings the interrupt/steer-fallback path in line.

The sibling defect in #43066 (assistant messages lost after compaction)
was already fixed on main by the identity-tracking flush rewrite (#46053)
plus the pre-rotation flush (#47202), so this only addresses the
remaining busy-message-merge half.

Co-authored-by: KiruyaMomochi <65301509+KiruyaMomochi@users.noreply.github.com>
… call

The subagent-demotion busy-handler test asserted the internal
merge_pending_message_event call, which the FIFO refactor replaced with
_queue_or_replace_pending_event. Assert the behavioral outcome (the
follow-up lands in the pending slot for the next turn) instead — same
fix already applied to the two steer-fallback tests.
…s rotation

Three state-loss bugs at the compression rotation boundary, fixed together
because they all live in the same ~80-line rotation block:

- #33618: a persistent /goal did not follow the rotation. load_goal does a
  flat per-session lookup with no lineage walk, so a goal silently died when
  compression minted a fresh child id. Added migrate_goal_to_session() and
  call it after the child session is created (move-not-copy: the parent row
  is archived as cleared so exactly one active goal row exists).

- #33906/#33907: if the child create_session raised (FK constraint,
  contended write), the outer handler only warned and let the agent continue
  on the NEW id — which has no row in state.db — producing an orphan session.
  Now the rotation rolls agent.session_id back to the still-indexed parent
  (reopening it) instead of stranding the conversation on a phantom id.

- #27633: the compaction-boundary on_session_start notification omitted the
  platform kwarg, so context-engine plugins saw source=unknown for every
  message after the boundary. Forward platform (matching the initial
  session-start call in agent_init.py).

Co-authored-by: denisqq <21260182+denisqq@users.noreply.github.com>
Co-authored-by: zccyman <16263913+zccyman@users.noreply.github.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Closes #48835

The bundled himalaya skill and its website docs documented command
syntax that does not match Himalaya CLI v1.2.0.

Verified against pimalaya/himalaya v1.2.0 source:
- message move: MessageMoveCommand declares target_folder BEFORE
  envelopes (src/email/message/command/move.rs) -> usage is
  '<TARGET> <ID>...', so 'move 42 "Archive"' is wrong; correct is
  'move "Archive" 42'.
- message copy: same ordering in copy.rs.
- attachment download: AttachmentDownloadCommand exposes the flag as
  '-d, --downloads-dir <PATH>' (src/email/message/attachment/command/
  download.rs), not '--dir'.

Fixed in all three surfaces that carried the wrong examples:
- skills/email/himalaya/SKILL.md
- website/docs/.../email-himalaya.md
- website/i18n/zh-Hans/.../email-himalaya.md
…tive PowerShell

The Chinese README still told Windows users to install WSL2 and run
the Linux installer. Hermes now ships a native PowerShell install
script, so replace the outdated WSL2-only note with the direct
PowerShell one-liner.

Fixes: documentation accuracy / Windows onboarding
CONTRIBUTING.md had no pre-work search step; the only duplicate-check is a
PR-template checkbox that fires at review time, after the work is already done.
Add a "Before You Start: Search First" section near the top so contributors
search open and merged PRs and issues (and the source, since the tracker can
lag the code) before building. References #38284 (the agent-side analog).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When model.context_length is set in config.yaml, it blocks auto-detection
from the server's /v1/models endpoint. The skill incorrectly implied a
hard fallback to 131072. Add the resolution chain and the fix command
(hermes config set model.context_length "") to both the config table
and a new troubleshooting section.
teknium1 and others added 29 commits June 21, 2026 18:05
…ct provider (#50480)

* fix(agent): strip stale reasoning_content when falling back to a strict provider

A reasoning primary (DeepSeek/Kimi/MiMo thinking mode) pins reasoning_content
on every assistant tool-call turn (a single space " " pad). api_messages is
built once under the primary; on a mid-session fallback to a strict
OpenAI-compatible provider (Mistral, Cerebras, Groq, SambaNova), those stale
pads were replayed verbatim and rejected with HTTP 400/422:

    body.messages.2.assistant.reasoning_content: Extra inputs are not
    permitted  (input: ' ')

reapply_reasoning_echo_for_provider() only ever ADDED pads, so it never
reconciled history built under a reasoning primary against a strict fallback.
copy_reasoning_content_for_api() also leaked empty-string and 'reasoning'-only
shapes to non-pad providers.

Fix both sites: when the active provider does not enforce echo-back, strip
reasoning_content (empty, space-pad, or non-empty) entirely. Re-padding when
switching TO a reasoning provider is preserved. Covers the Cerebras 400 from
#45655 and the DeepSeek->Mistral 422 fallback report.

Refs #45655.

* test: update reasoning-replay tests for strict-provider stripping

test_explicit_reasoning_content_beats_normalized_reasoning_on_replay was
implicitly running on the OpenRouter fixture (non-pad); pin it to a reasoning
provider so the precedence it checks is observable. Add a positive
strict-provider test asserting reasoning_content is stripped on replay.
logging.basicConfig() in TrajectoryCompressor.__init__ overrides the
root logger configuration every time the class is instantiated. Library
code should use logging.getLogger(__name__) and let the application
entry point configure the root logger.

Fixes inconsistent log formatting when the compressor is used alongside
other logging configuration in the gateway.
… main

Same library-code anti-pattern as the compressor fix: MiniSWERunner.__init__
called logging.basicConfig(), overriding the application's root logger config
every time a runner was instantiated. Moved the call into main() (the CLI
entry point) where it belongs; __init__ now only does getLogger(__name__).
Standalone verbose logging is preserved.
Remove the dashboard --insecure auth-bypass, add an MCP persistence guard +
IOC blocklist, and raise the API-server key entropy floor.

Driven by the June 2026 hermes-0day campaign (r/hermesagent, live 854.media
instance): scanners find exposed Hermes dashboards/API servers, drive the
root agent to plant a 'command: bash' MCP entry that appends an attacker SSH
key to authorized_keys, which cron + startup then re-execute every tick.

- dashboard: --insecure no longer disables the auth gate. should_require_auth
  returns True for every non-loopback bind; a public bind ALWAYS requires an
  auth provider (bundled password provider or OAuth). --insecure kept as a
  warned no-op for backward compat. Fail-closed error now points at the
  password provider, not at --insecure.
- mcp_security: validate_mcp_server_entry now also rejects shell payloads that
  write to OS persistence surfaces (authorized_keys/.ssh/pam.d/sudoers/cron/
  rc files) and hard-rejects a hermes-0day IOC blocklist (attacker SSH key +
  source IPs) anywhere in command/args/env. Runs at save AND spawn time.
- api_server: raise network-bind API_SERVER_KEY entropy floor 8->16 chars;
  warn when a network-accessible API server runs an unsandboxed local backend.
The s6 dashboard entrypoint and docker integration tests relied on
HERMES_DASHBOARD_INSECURE=1 to bring up a 0.0.0.0 dashboard with no auth
provider. With --insecure now a no-op (auth gate mandatory on non-loopback
binds), that path fails closed.

- s6 dashboard/run: drop --insecure derivation; warn that the env is a no-op
  and point operators at HERMES_DASHBOARD_BASIC_AUTH_* / OAuth.
- docker tests: supervision tests now register the bundled basic password
  provider (HERMES_DASHBOARD_BASIC_AUTH_USERNAME/_PASSWORD) so the gate has a
  provider and the dashboard binds. Rewrote the insecure-opt-out test to
  assert fail-closed (dashboard does NOT serve) instead of gate-bypass.
- docs (en + zh-Hans): HERMES_DASHBOARD_INSECURE documented as deprecated
  no-op; basic-auth is the zero-infra way to authenticate a containerized
  public dashboard.
Surface dangerous host/deployment posture at gateway startup so operators get
the 'you're exposed' signal the June 2026 MCP-config persistence campaign
victims never had. Warn-only — never blocks startup, never raises.

Checks (each independently fail-safe):
- Running as root (POSIX uid 0)
- SSH daemon with PasswordAuthentication enabled (incl. the 'yes' default)
- Running in a container with no persistent volume mount over HERMES_HOME
- Network-accessible API server with no API_SERVER_KEY

New module hermes_cli/security_audit_startup.py; invoked once per process from
start_gateway() right after setup_logging(). Cross-platform (root/SSH checks
no-op on Windows). Idea: @cthulhu.
…r grace

A daemon that ignores or stalls in its SIGTERM handler currently survives the
process-registry reap and leaks until reboot (observed as agent-browser
daemons accumulating to EMFILE on long-running gateways). _terminate_host_pid
now snapshots the tree, SIGTERMs it, waits a bounded grace window
(terminal.daemon_term_grace_seconds, default 2.0s, 0 disables), then SIGKILLs
any survivor. The recycled-PID identity guard still gates the whole path, so
escalation never reaches a stranger; Windows is unchanged (taskkill /F is
already a hard kill).

Config lives in config.yaml (terminal.daemon_term_grace_seconds), NOT an env
var, per the .env-secrets-only policy.

Implements the SIGKILL-escalation idea from @tkwong's #15008, reworked onto the
current _terminate_host_pid tree-kill path (the original predated it) and
config-gated instead of env-var-gated.

Co-authored-by: Benjamin Wong <tkwong@inspiresynergy.com>
…cs survivors

Live testing against a real SIGTERM-ignoring process TREE (parent + children,
the agent-browser daemon + renderer shape) revealed psutil.wait_procs's
gone/alive partition mis-handles a parent/child tree: it reaps via
Process.wait() and could mark targets gone/alive inconsistently across the
tree, leaving survivors un-killed (flaky — sometimes the parent lived,
sometimes a child). Replace it with: sleep out the grace window, then
directly re-probe every captured target (_proc_alive, treating zombies as
dead) and SIGKILL any that's still running. Add a multi-child-tree regression
test. 6/6 escalation tests green across repeated runs; the real-tree E2E now
kills the full tree 6/6 runs.
…#50497)

The welcome banner's 'Available Tools' merged in every toolset from the
global check_tool_availability() registry walk, regardless of whether it
was enabled for the current platform. On a Blank Slate CLI (file +
terminal only) that surfaced discord / feishu / kanban tools the agent
was never actually given — they are not in the agent's tool schema, but
the banner displayed them, making it look like they were exposed.

- Filter the unavailable-toolset merge to toolsets actually in
  enabled_toolsets (a toolset that's enabled but has unmet deps still
  legitimately shows as disabled/lazy).
- Gate the 'Available Skills' section on the skills toolset being
  enabled — when it's off, the agent can't load any skill, so show
  'Skills toolset disabled' instead of the on-disk catalog.

When enabled_toolsets is empty (older callers), behavior is unchanged.

Validation: blank-slate banner now shows only file + terminal and
'Skills toolset disabled'; a skills-enabled banner still lists the
catalog. Added regression tests; full banner suite green (15/15).
…providers (#50492)

* feat(providers): remove google-gemini-cli + google-antigravity OAuth providers

Google now actively bans accounts for third-party tools that piggyback on
Gemini CLI / Antigravity / Code Assist OAuth, and because abuse prevention
sits at a backend layer the ban can extend to the entire Google account
(Gmail/Drive), with a second violation being permanent.
Ref: google-gemini/gemini-cli#20632

Removes both OAuth inference providers entirely (modules, provider profiles,
auth/runtime/config/models wiring, the /gquota Code Assist quota command,
the antigravity-cli optional skill, desktop + docs surface in en + zh-Hans).
The API-key 'gemini' provider (GOOGLE_API_KEY/GEMINI_API_KEY against
generativelanguage.googleapis.com) is unaffected and stays fully supported.

* fix(skills): keep the antigravity-cli skill — only the OAuth provider is removed

The antigravity-cli optional skill orchestrates the external `agy` binary as
a coding-agent tool via the terminal tool — it does NOT wrap Hermes inference
through the banned google-antigravity OAuth provider, so it carries none of
the account-ban risk that motivated removing that provider. Restore the skill,
its docs page, the sidebar entry, and the optional-skills catalog row. The
google-antigravity / google-gemini-cli inference providers stay fully removed.
gateway/platforms/telegram.py no longer exists (adapters moved to
plugins/platforms/<name>/adapter.py) and telegram no longer uses the
scoped-lock pattern. Point the token-lock canonical-pattern reference to
plugins/platforms/irc/adapter.py, which acquires the lock in connect()
and releases it in disconnect() — and is already cited as a canonical
example in ADDING_A_PLATFORM.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ns/platforms

Sibling-site follow-up to the AGENTS.md token-lock fix (#50481). Platform
adapters migrated from gateway/platforms/<name>.py to
plugins/platforms/<name>/adapter.py; a handful (signal, weixin, bluebubbles,
qqbot, yuanbao, msgraph_webhook, webhook, api_server) still live in
gateway/platforms/.

- adding-platform-adapters.md: new-adapter creation path + reference-impl table
- gateway-internals.md: rewrite the adapter tree to reflect the actual split
- zh-Hans mirrors of both kept in parity
- scripts/release.py: add TutkuEroglu to AUTHOR_MAP (CI gate)
…0534)

Plugins shelling out to bare `hermes` via the terminal tool hit
`command not found` (exit 127) when the gateway was launched without the
hermes install dir on PATH (systemd, service managers, cron, desktop
launchers) — even though `hermes` works in the user's own interactive
terminal, which sources the shell rc that exports that dir.

The terminal tool's subshell PATH was the agent process PATH plus a
static set of system dirs (_SANE_PATH); it never included wherever the
hermes console-script actually lives (~/.local/bin, the venv bin/Scripts,
pipx, nix). Resolve that dir once (which/argv0/sys.executable) and
prepend-if-missing it so bare `hermes` resolves regardless of launch
method.
…p (#50499)

* feat(cli): /reasoning full to show complete thinking, not 10-line clamp

The post-response Reasoning recap box hard-clamped long thinking to the
first 10 lines, so there was no way to see the full reasoning trace after
a turn (live streaming already shows it in full). Add display.reasoning_full
(default off) plus /reasoning full|clamp to toggle it at runtime; the clamp
truncation note now points at the command. Addresses repeated user requests
to show all thinking tokens.

* test(gateway): de-snapshot /reasoning help assertion

The test froze the exact args-hint literal '/reasoning [level|show|hide]',
which the new full/clamp args change to '[level|show|hide|full|clamp]'.
Convert to an invariant: assert /reasoning is in help and carries its core
args, not the exact hint string.

* feat(tui): /reasoning full|clamp parity in tui_gateway

The classic-CLI reasoning_full toggle had no TUI equivalent — typing
/reasoning full in the TUI fell through to parse_reasoning_effort and
errored. The TUI renders thinking as an expand/collapse section (no fixed
10-line recap), so map full -> sections.thinking=expanded (raw, uncapped
via thinkingPreview mode='full') and clamp -> collapsed, persisting
display.reasoning_full for cross-surface config consistency.
* feat(cli): /prompt — compose your next prompt in $EDITOR

Adds /prompt (alias /compose): opens $VISUAL/$EDITOR on a temp markdown
file so you can hand-edit a multi-line prompt, then sends the saved buffer
as the next agent turn. Text after the command pre-seeds the buffer; an
empty save cancels. Reuses the one-shot _pending_agent_seed the interactive
loop already consumes (same mechanism as /blueprint), so no changes to the
input event loop or message pipeline. CLI-only.

* feat(tui): /prompt slash command opens $EDITOR (parity with CLI)

The TUI already opens $EDITOR via Ctrl+G (openEditor), but had no /prompt
slash command like the classic CLI. Wire openEditor into the slash handler
context and register /prompt (alias /compose) to call it; inline text after
the command is dropped into the composer first so it carries into the editor,
matching the CLI's /prompt <text>.
…ind (#50551)

When `hermes dashboard --host 0.0.0.0` is run interactively with the auth
gate engaged but no DashboardAuthProvider configured, prompt to set up the
bundled username/password provider on the spot (or point at `hermes dashboard
register` for OAuth) instead of only emitting the fail-closed error.

- main.py: `_maybe_setup_dashboard_auth_interactively()` runs before
  start_server. No-ops on loopback binds, when a provider is already
  registered, or when stdin/stdout isn't a TTY (Docker/s6, CI, piped runs) so
  the fail-closed SystemExit stays the backstop for unattended deploys. On the
  password path it writes dashboard.basic_auth.{username,password_hash,secret}
  to config.yaml (scrypt hash, never plaintext), then force-rediscovers
  plugins so the basic provider registers before the gate check.
- web_server.py: fix the fail-closed hint — it told operators to set
  `dashboard_auth.basic.username` but the provider reads `dashboard.basic_auth`.
- docs: note the interactive setup under Fail-closed semantics.

No new env vars; reuses the existing dashboard.basic_auth config surface.
…50600)

* fix(gateway): walk /proc/*/cmdline to find main-wrapper.sh under s6-overlay v3 (#49196)

(cherry picked from commit 3a108c2df0edce4ce0e6f9f3a8eb8db3839a4630)

* fix(container): peel s6-v3 rc.init prefix so dashboard role is detected

kyssta-exe's preceding commit (#49238) fixed _read_container_argv() to
locate the rc.init-launched main-wrapper.sh process under s6-overlay v3,
but the skip still never fired: _strip_container_argv_prefix() only peeled
a prefix when args[0] was init/main-wrapper.sh/hermes. Under s6 v3 the
matched argv is

    /bin/sh -e /run/s6/basedir/scripts/rc.init top
        /opt/hermes/docker/main-wrapper.sh dashboard ...

so args[0] stayed /bin/sh, _is_dashboard_container() returned False, and
the dashboard container reconciled + started its own gateway-default —
the exact dual Telegram getUpdates 409 in issue #49196.

Fix: strip everything up to and including the main-wrapper.sh token (the
stable boundary the image owns), covering both the v2 (/init ...) and v3
(/bin/sh ... rc.init top ...) shapes with one rule, instead of matching
launcher tokens positionally. This also repairs _is_legacy_gateway_run_request()
under v3, which shares the same strip helper (the issue called this out).

Tests: extend the dashboard true/false parametrize sets with the s6-v3
argv shape, and add test_main_skips_reconcile_in_dashboard_container_s6v3
exercising main() end-to-end with the v3 argv. Verified via mutation that
both new v3 assertions fail under the old positional strip and pass with
the fix.

---------

Co-authored-by: kyssta-exe <kyssta-exe@users.noreply.github.com>
… in CLI + gateway (#50631)

terminal.docker_extra_args passes flags verbatim to `docker run` (e.g.
--gpus=all, --shm-size=16g). It was wired into DEFAULT_CONFIG,
TERMINAL_CONFIG_ENV_MAP (so `hermes config set` bridged it),
terminal_tool._get_env_config (reads TERMINAL_DOCKER_EXTRA_ARGS), and
DockerEnvironment (applies extra_args) -- but it was MISSING from cli.py's
env_mappings and gateway/run.py's _terminal_env_map.

Consequence: a user who hand-edits config.yaml (rather than running
`hermes config set`) has docker_extra_args silently dropped on the CLI and
gateway/desktop startup paths, while docker_image / docker_volumes (which
ARE in those maps) bridge correctly -- producing the reported 'Hermes
partially reads the Docker config' symptom where --gpus=all and
--shm-size=16g never reach docker run.

This is the same bridge-coverage bug class that shipped before for
docker_run_as_host_user (cli + gateway) and docker_mount_cwd_to_workspace
(gateway). Fix by adding the key to both maps, plus a dedicated regression
pin in test_terminal_config_env_sync.py mirroring the existing
test_docker_*_is_bridged_everywhere guards.
…orms

Authorization to message the agent is the gate, not the file extension.
Previously the inbound-attachment allowlist (SUPPORTED_DOCUMENT_TYPES) was
opt-OUT on Discord (allow_any_attachment defaulted false) and had no bypass
at all on Telegram/Slack — so an .html (or any non-allowlisted type) was
dropped or hard-rejected before the agent saw it.

Now every authorized upload is cached and surfaced to the agent regardless
of type:
- base.cache_media_bytes(): unknown types cache as octet-stream (or the
  caller-supplied MIME) instead of returning None — fixes the chokepoint
  that Teams/Telegram-media route through.
- discord/telegram/slack adapters: removed the allowlist reject/skip; any
  non-media attachment is typed DOCUMENT and cached. Known types keep their
  precise MIME.
- Text inlining now gates on a shared _TEXT_INJECT_EXTENSIONS set (text +
  code + config + markup) instead of a blind UTF-8 decode, so binary formats
  (PDF/zip/docx) with ASCII headers are never inlined.
- gateway/run.py emits the path-pointing context note for every DOCUMENT,
  including non text/application MIME types.
- discord.allow_any_attachment is now a documented no-op kept for config
  back-compat.

Validation: 357 gateway tests pass; E2E confirms .html/.bin/custom types
cache, known types stay precise, PDFs are not inlined.
Follow-up to the accept-any-file-type change. The observe-unmentioned and
replied-media paths relied on cache_media_bytes() returning None for
unsupported document types to emit an 'unsupported, not cached' note. Now
that any file type is always cached, those docs are cached and surfaced with
a path-pointing note — consistent with the main document path. The
remaining cached-is-None branch is image-validation-failure only; its note
is reworded accordingly. Updates the group-gating test to the new contract.
Ctrl+G already opened $EDITOR with the current draft, but used
open_in_editor(validate_and_handle=False), which only loaded the saved text
back into the input area — the user still had to press Enter. The TUI's
Ctrl+G (openEditor) submits the draft on a clean exit. Since CLI submission
is driven by the custom Enter keybinding (not the buffer accept_handler),
validate_and_handle can't route through it; instead chain a done-callback on
the editor Task that calls the new _submit_editor_buffer(), which mirrors the
Enter handler's idle/queue/slash branches and drops an empty save.
display.timestamps already drove the [HH:MM] suffix on live submitted and
streamed message labels, but there was no runtime command to toggle it and
/history ignored the setting entirely. Add /timestamps [on|off|status]
(alias /ts) and render [HH:MM] in /history for turns that carry a stored
unix timestamp (resumed sessions). Live unsaved turns without a stored time
are never given a fabricated one. Uses the existing sanctioned non-wire
'timestamp' message key (stripped before the API call in chat_completions),
so message-alternation and prompt-cache invariants are untouched.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: upstream-sync/2026-06-22-5ff11a689 vs origin/main

ruff

Total: 334 on HEAD, 0 on base (🆕 +334)

🆕 New issues (47):

Rule Count
unknown 47
First entries
tools/skill_manager_tool.py:744: [unknown] Expected `,`, found `>>`
plugins/platforms/slack/adapter.py:5077: [unknown] unindent does not match any outer indentation level
gateway/config.py:1083: [unknown] Expected `except` or `finally` after `try` block
tools/skill_manager_tool.py:741: [unknown] Expected `:`, found `,`
tests/gateway/test_display_config.py:504: [unknown] Expected `,`, found string
tools/skill_manager_tool.py:931: [unknown] Expected a statement
tests/tools/test_skill_manager_tool.py:1326: [unknown] Expected a statement
tools/skill_manager_tool.py:212: [unknown] Simple statements must be separated by newlines or semicolons
tools/skill_manager_tool.py:742: [unknown] Expected `,`, found `==`
tools/skill_manager_tool.py:745: [unknown] Expected `:`, found `}`
plugins/platforms/slack/adapter.py:5406: [unknown] Invalid annotated assignment target
tools/skill_manager_tool.py:742: [unknown] Expected `,`, found `=`
tools/skill_manager_tool.py:741: [unknown] Expected `,`, found `:`
tools/skill_manager_tool.py:744: [unknown] Expected `,`, found `>`
gateway/config.py:1193: [unknown] Expected a statement
agent/prompt_builder.py:1725: [unknown] Expected a statement
plugins/platforms/slack/adapter.py:5407: [unknown] Expected dedent, found end of file
tools/send_message_tool.py:1500: [unknown] Expected a statement
tests/gateway/test_display_config.py:566: [unknown] Expected a statement
tools/skill_manager_tool.py:193: [unknown] Compound statements are not allowed on the same line as simple statements
plugins/platforms/slack/adapter.py:5095: [unknown] Unexpected indentation
tools/send_message_tool.py:950: [unknown] Expected an indented block after `if` statement
tools/skill_manager_tool.py:251: [unknown] Expected an expression
tests/gateway/test_display_config.py:505: [unknown] Expected `,`, found `:`
tests/tools/test_send_message_target_parse.py:95: [unknown] Expected a statement
... and 22 more

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 12067 on HEAD, 10971 on base (🆕 +1096)

🆕 New issues (764):

Rule Count
unresolved-import 196
unresolved-attribute 174
invalid-argument-type 81
unresolved-reference 75
invalid-syntax 53
invalid-type-form 50
not-subscriptable 35
invalid-assignment 35
unsupported-operator 28
invalid-method-override 18
invalid-return-type 7
unused-type-ignore-comment 3
call-non-callable 3
no-matching-overload 2
invalid-raise 1
+3 more rules
First entries
tests/tools/test_clarify_tool.py:213: [invalid-argument-type] invalid-argument-type: Argument to function `clarify_tool` is incorrect: Expected `list[str] | None`, found `list[str | dict[str, str]]`
tests/tools/test_browser_hardening.py:125: [not-subscriptable] not-subscriptable: Cannot subscript object of type `int` with no `__getitem__` method
plugins/platforms/feishu/feishu_comment.py:41: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi.core.model.base_request`
plugins/platforms/telegram/adapter.py:134: [unresolved-import] unresolved-import: Module `telegram` has no member `Bot`
tools/delegate_tool.py:2554: [invalid-argument-type] invalid-argument-type: Argument to function `dispatch_async_delegation_batch` is incorrect: Expected `list[str]`, found `list[Any | str | None | list[str]]`
tests/hermes_cli/test_managed_scope_loaders.py:93: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `None` in union `dict[Unknown, Unknown] | None`
plugins/platforms/wecom/callback_adapter.py:33: [unresolved-import] unresolved-import: Cannot resolve imported module `aiohttp`
tests/agent/test_compression_rotation_state.py:56: [unresolved-attribute] unresolved-attribute: Unresolved attribute `context_compressor` on type `AIAgent`
tools/send_message_tool.py:991: [invalid-syntax] invalid-syntax: Expected an expression
tests/run_agent/test_run_agent.py:5873: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_disable_streaming` on type `AIAgent`
plugins/platforms/wecom/adapter.py:1572: [unresolved-import] unresolved-import: Cannot resolve imported module `qrcode`
tests/gateway/relay/test_relay_registration.py:12: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_pre_gateway_dispatch.py:44: [invalid-type-form] invalid-type-form: Variable of type `Never` is not allowed in a parameter annotation
tests/gateway/test_display_config.py:535: [invalid-syntax] invalid-syntax: Invalid annotated assignment target
tests/gateway/relay/test_relay_roundtrip.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
gateway/run.py:16006: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `str`, found `Any | str | int | float | list[dict[str, Any]]`
plugins/platforms/matrix/adapter.py:1858: [invalid-method-override] invalid-method-override: Invalid override of method `send_document`: Definition is incompatible with `BasePlatformAdapter.send_document`
tests/test_tui_gateway_server.py:3312: [unresolved-attribute] unresolved-attribute: Attribute `append` is not defined on `int` in union `int | list[Unknown]`
tests/agent/test_secret_scope.py:2: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_browser_hardening.py:125: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method
tests/hermes_cli/test_default_interface_resolution.py:191: [not-subscriptable] not-subscriptable: Cannot subscript object of type `float` with no `__getitem__` method
plugins/platforms/dingtalk/adapter.py:137: [unresolved-import] unresolved-import: Cannot resolve imported module `dingtalk_stream.frames`
plugins/platforms/slack/adapter.py:536: [invalid-type-form] invalid-type-form: Variable of type `Never` is not allowed in a parameter annotation
gateway/relay/ws_transport.py:181: [unresolved-attribute] unresolved-attribute: Attribute `connect` is not defined on `None` in union `Unknown | None`
tests/agent/test_turn_finalizer_cleanup_guard.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
... and 739 more

✅ Fixed issues (348):

Rule Count
unresolved-attribute 123
unresolved-import 73
invalid-argument-type 59
unsupported-operator 27
invalid-assignment 25
invalid-method-override 20
invalid-return-type 5
no-matching-overload 4
call-non-callable 3
unknown-argument 2
unresolved-reference 2
unresolved-global 1
not-subscriptable 1
unused-type-ignore-comment 1
invalid-parameter-default 1
+1 more rules
First entries
gateway/platforms/whatsapp.py:156: [unresolved-import] unresolved-import: Cannot resolve imported module `psutil`
gateway/platforms/matrix.py:1784: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
gateway/platforms/telegram.py:4857: [unresolved-attribute] unresolved-attribute: Attribute `SUPERGROUP` is not defined on `None` in union `Unknown | None`
tests/agent/test_gemini_cloudcode.py:246: [unresolved-attribute] unresolved-attribute: Attribute `project_id` is not defined on `None` in union `GoogleCredentials | None`
gateway/run.py:11869: [invalid-argument-type] invalid-argument-type: Argument to bound method `set.add` is incorrect: Expected `tuple[str, str, str | None]`, found `tuple[Literal["local", "telegram", "discord", "whatsapp", "whatsapp_cloud", ... omitted 18 literals] | set[Unknown], str, str | None]`
gateway/platforms/matrix.py:1246: [unresolved-import] unresolved-import: Cannot resolve imported module `mautrix.crypto`
gateway/platforms/wecom.py:372: [unresolved-attribute] unresolved-attribute: Attribute `WSMsgType` is not defined on `None` in union `Unknown | None`
gateway/platforms/feishu.py:3537: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_last_chunk_len` on type `MessageEvent`
gateway/platforms/dingtalk.py:1067: [unresolved-attribute] unresolved-attribute: Attribute `CreateCardHeaders` is not defined on `None` in union `Unknown | None`
gateway/platforms/feishu.py:3281: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
gateway/platforms/feishu.py:1369: [unresolved-import] unresolved-import: Cannot resolve imported module `lark_oapi.core.const`
agent/gemini_cloudcode_adapter.py:523: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `None` in union `Any | None | dict[str, Any]`
agent/display.py:303: [invalid-argument-type] invalid-argument-type: Argument to function `_resolve_skill_dir` is incorrect: Expected `str`, found `Unknown | None`
gateway/platforms/telegram.py:138: [unresolved-import] unresolved-import: Cannot resolve imported module `telegram.ext`
gateway/platforms/whatsapp.py:1019: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_last_chunk_len` on type `MessageEvent`
gateway/platforms/matrix.py:1141: [unresolved-import] unresolved-import: Cannot resolve imported module `mautrix.client.state_store`
tests/skills/test_google_oauth_setup.py:109: [unresolved-attribute] unresolved-attribute: Unresolved attribute `flow` on type `ModuleType`
gateway/platforms/dingtalk.py:1051: [unresolved-attribute] unresolved-attribute: Attribute `CreateCardRequestCardData` is not defined on `None` in union `Unknown | None`
gateway/platforms/wecom_callback.py:271: [unresolved-attribute] unresolved-attribute: Attribute `Request` is not defined on `None` in union `Unknown | None`
agent/auxiliary_client.py:3124: [unknown-argument] unknown-argument: Argument `api_key` does not match any known parameter of function `resolve_provider_client`
tests/gateway/test_busy_session_auth_bypass.py:44: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `Platform`, found `MagicMock`
hermes_cli/dump.py:236: [invalid-assignment] invalid-assignment: Object of type `Literal["(unknown)"]` is not assignable to `Literal["0.16.0"]`
gateway/platforms/slack.py:4560: [invalid-return-type] invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `str`
gateway/platforms/dingtalk.py:921: [unresolved-attribute] unresolved-attribute: Attribute `TimeoutException` is not defined on `None` in union `Unknown | None`
tests/cron/test_suggestions.py:197: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["monitor"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 33 union elements`
... and 323 more

Unchanged: 5389 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.