Skip to content

feat(teams): Microsoft Teams platform adapter as a plugin (+ xdist collision guard)#17828

Merged
teknium1 merged 7 commits into
mainfrom
hermes/hermes-31d5c206
Apr 30, 2026
Merged

feat(teams): Microsoft Teams platform adapter as a plugin (+ xdist collision guard)#17828
teknium1 merged 7 commits into
mainfrom
hermes/hermes-31d5c206

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvage of #17764 by @heyitsaamir onto current main, with a follow-up commit that fixes the CI failures and prevents the regression from recurring.

Summary

Adds the Microsoft Teams platform adapter as a plugin under plugins/platforms/teams/, and makes it impossible for any future plugin-adapter test to reintroduce the sys.path/sys.modules collision that broke CI.

What the contributor's 6 commits do (preserved authorship)

  • plugins/platforms/teams/adapter.py (~685 LOC) — Teams adapter implementation
  • plugins/platforms/teams/plugin.yaml, __init__.py — plugin wiring
  • tests/gateway/test_teams.py — test suite
  • tools_config.py — handle plugin platforms in platform_tool_universe
  • Interactive setup, image attachments, card visibility, send_image_file override
  • .env.example, docker-compose.yml, cli-config.yaml.example updates
  • website/docs/user-guide/messaging/teams.md docs

My follow-up commit

1. Fix the root cause of the CI test failure

test_teams.py and test_irc_adapter.py (already on main) both did:

sys.path.insert(0, str(_REPO_ROOT / 'plugins' / 'platforms' / '<name>'))
from adapter import <Adapter>

Every plugin ships its own adapter.py, so the bare import adapter races for sys.modules['adapter']. Whichever collects first in an xdist worker wins; the other crashes with ImportError, and the polluted sys.path cascades into 19 unrelated test failures in that worker (test_resolve_path, test_modal_sandbox_fixes, test_web_server, etc.).

2. Shared helper (tests/gateway/_plugin_adapter_loader.py)

load_plugin_adapter('<name>') imports the plugin's adapter.py via importlib.util.spec_from_file_location under a unique module name (plugin_adapter_<name>). Zero sys.path mutation.

3. Both plugin tests migrated to the helper

Removed all sys.path.insert tricks and the 7 in-method from adapter import register calls.

4. Anti-pattern guard (tests/gateway/conftest.py)

New pytest_configure hook (runs on xdist controller before workers spawn) AST-scans every test_*.py under tests/gateway/ and fails the session with a clear remediation message if it finds:

  • sys.path.insert/append/extend(...) pointing into plugins/platforms/ (catches both string-literal and Path(...) / 'plugins' / 'platforms' forms), OR
  • bare import adapter / from adapter import ...

The next plugin adapter test that tries the old pattern gets rejected at collection time with a pointer to load_plugin_adapter.

5. scripts/release.py

Add aamirjawaid@microsoft.comheyitsaamir to AUTHOR_MAP so check-attribution passes.

Validation

Scenario Result
Full tests/gateway/ suite 4194 passed (only 1 pre-existing flaky whatsapp test)
test_teams.py + test_irc_adapter.py both orderings 72/72
11 previously-cascaded test files 398/398
Planted test with sys.path + plugins/platforms + bare from adapter rejected at collection
Planted test with string-literal form "/…/plugins/platforms/…" also rejected

Closes

Supersedes #17764. Original authorship preserved via rebase-merge.

Aamir Jawaid and others added 7 commits April 30, 2026 01:01
Hello! I am the maintainer of the microsoft-teams-apps Python SDK and
I built this Teams adapter to integrate Microsoft Teams into Hermes.

Adds a `plugins/platforms/teams` platform plugin using the new
PlatformRegistry system from #17751. The adapter self-registers via
`register(ctx)` — no hardcoding in run.py, toolsets.py, or any
other core file.

Key features:
- Supports personal DMs, group chats, and channel posts
- Adaptive Card approval prompts with in-place button replacement
  (Allow Once / Allow Session / Always Allow / Deny)
- aiohttp webhook server bridged from the Teams SDK to avoid
  the fastapi/uvicorn dependency
- ConversationReference caching for correct proactive sends in
  non-DM chats
- `interactive_setup()` for `hermes gateway setup` integration
- `platform_hint` for LLM context (Teams markdown subset)
- 34 tests covering adapter init, send, message handling, and
  plugin registration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_get_platform_tools() correctly fell back to f"hermes-{platform}" for
unknown (plugin) platforms when building toolset_names, but then
unconditionally used PLATFORMS[platform] again for platform_tool_universe,
causing KeyError for any plugin-registered platform like Teams.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Teams doesn't render markdown image syntax. Send images using the SDK's
Attachment API instead — base64 data URI for local files, direct URL
for remote images.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The gateway calls send_image_file() for locally cached images
(e.g. from image_gen tools). Without this override the base class
falls back to sending the file path as plain text. Delegate to
send_image() which already handles base64 encoding local paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass cmd/desc in button action data so the card response can
reconstruct the original body. Clicking a button now replaces
only the actions with a status line, keeping the command and
reason text visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the Azure portal credential prompts with the teams CLI
workflow: install @microsoft/teams.cli, run teams app create,
paste the output credentials. Matches the setup docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes the xdist collision that broke CI on PR #17764, and structurally
prevents future plugin-adapter tests from reintroducing it.

Problem
-------
tests/gateway/test_teams.py (new in this PR) and tests/gateway/test_irc_adapter.py
(already on main) both followed the same anti-pattern:

  sys.path.insert(0, str(_REPO_ROOT / 'plugins' / 'platforms' / '<name>'))
  from adapter import <Adapter>

Every platform plugin ships its own adapter.py, so the bare
'from adapter import ...' races for sys.modules['adapter']. Whichever test
collected first in a given xdist worker won; the other crashed at
collection with ImportError, and the polluted sys.path cascaded into 19
unrelated test failures across tools/, hermes_cli/, and run_agent/ in the
same worker.

Fix
---
1. tests/gateway/_plugin_adapter_loader.py (new): shared helper
   load_plugin_adapter('<name>') that imports plugins/platforms/<name>/adapter.py
   via importlib.util under the unique module name plugin_adapter_<name>.
   Zero sys.path mutation, no possibility of collision.

2. tests/gateway/test_irc_adapter.py and tests/gateway/test_teams.py:
   migrated to the helper. All 'from adapter import ...' statements
   (including the ones inside test methods) are replaced with module-level
   attribute access on the loaded module.

3. tests/gateway/conftest.py: new pytest_configure guard that AST-scans
   every test_*.py under tests/gateway/ at session start and fails the
   run with a pointer to the helper if any test uses sys.path.insert into
   plugins/platforms/ OR a bare 'import adapter' / 'from adapter import'.
   Runs on the xdist controller only (skipped in workers). The next plugin
   adapter test that tries to reintroduce this pattern gets rejected at
   collection time with a clear remediation message.

4. scripts/release.py: add aamirjawaid@microsoft.com -> heyitsaamir to
   AUTHOR_MAP so the check-attribution workflow passes.

Validation
----------
scripts/run_tests.sh tests/gateway/                    4194 passed
scripts/run_tests.sh tests/gateway/test_{teams,irc}*   72 passed (both orderings)
scripts/run_tests.sh <11 prev-failing test files>      398 passed
Guard triggers correctly on both Path-operator and string-literal forms
of the anti-pattern.
@teknium1 teknium1 merged commit 26787ce into main Apr 30, 2026
10 of 12 checks passed
@teknium1 teknium1 deleted the hermes/hermes-31d5c206 branch April 30, 2026 08:19
@alt-glitch alt-glitch added comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/feature New feature or request labels Apr 30, 2026
teknium1 added a commit that referenced this pull request Apr 30, 2026
…#17836)

Three narrow fixes targeting the remaining red checks after #17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
donramon77 added a commit to donramon77/hermes-agent that referenced this pull request May 4, 2026
Cross-checks against the Teams plugin PR (NousResearch#17828) surfaced four config
surfaces this PR was missing. Closes those gaps so the diff matches the
canonical bundled-plugin shape upstream maintainers expect.

- ``.env.example``: new GOOGLE_CHAT_* block (project, subscription, SA
  JSON, allowed-users, home channel) with a 4-step setup pointer to the
  user-guide doc.
- ``cli-config.yaml.example``: ``google_chat`` added to the supported
  platform key list, default toolset entry ``google_chat:
  [hermes-google-chat]``.
- ``docker-compose.yml``: GOOGLE_CHAT_* env vars exposed to the gateway
  service (commented by default, with a note about mounting the SA JSON
  via volumes).
- ``gateway/config.py``: ``_PLATFORM_CONNECTED_CHECKERS`` entry kept and
  documented as redundant with the plugin's ``_is_connected`` callback
  but required to satisfy ``test_platform_connected_checkers`` (the
  enum entry stays to avoid a ~15-call-site rewrite of
  ``Platform.GOOGLE_CHAT`` to ``Platform("google_chat")``).
- ``tests/gateway/test_google_chat.py``: docstring on the import block
  explaining why we don't use ``load_plugin_adapter`` like Teams does
  (our plugin has a companion ``oauth.py`` that uses relative imports;
  the helper doesn't set ``__package__``, so relative imports inside
  the loaded adapter fail). The fully-qualified package import is the
  shape the conftest anti-pattern guard accepts.

Tests: 130/130 google_chat green. Full ``tests/gateway/`` shows the
same pre-existing dingtalk/whatsapp missing-SDK failures unrelated to
this change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
donramon77 added a commit to donramon77/hermes-agent that referenced this pull request May 4, 2026
…gin pattern

Audit against Teams' PR NousResearch#17828 found we were touching ~12 core files that
Teams doesn't, almost all because google_chat originally landed as an
in-tree adapter and we kept those touches when migrating to a plugin.
This removes everything redundant with the plugin/registry path so the
diff matches the canonical bundled-plugin shape.

Removed (redundant — registry / generic plugin path covers it):
- ``agent/prompt_builder.py`` — PLATFORM_HINTS["google_chat"] entry; the
  plugin's ``register_platform(platform_hint=…)`` already feeds
  ``run_agent.py:_build_system_prompt`` for plugin platforms. Hint
  content folded into the registered platform_hint (and absorbs the
  session_context guardrails too).
- ``gateway/run.py`` — five dispatch table entries
  (``_builtin_allowed_users_vars``, ``_builtin_allow_all_vars``,
  ``platform_user_env_map``, ``platform_allow_all_map``, the second
  ``platform_env_map``); the registry-aware fallback at line ~3962
  picks up the plugin's ``allowed_users_env`` / ``allow_all_env``.
  Also removed the leftover ``elif platform == Platform.GOOGLE_CHAT:``
  comment tombstone.
- ``tools/send_message_tool.py`` — ``_send_google_chat`` (~50 LOC) and
  the ``Platform.GOOGLE_CHAT`` branch in ``_send_to_platform``; the
  generic ``_send_via_adapter`` fallback dispatches through the live
  plugin adapter.
- ``gateway/session.py`` — ``elif Platform.GOOGLE_CHAT:`` block in
  ``build_session_context_prompt``; the platform_hint subsumes those
  behavioral guardrails.
- ``toolsets.py`` — ``"hermes-google-chat"`` entry and its inclusion in
  ``hermes-gateway``; ``resolve_toolset`` auto-generates a toolset for
  any plugin platform via the registry (and the hyphenated form was
  unreachable anyway — plugin name is ``google_chat``).
- ``hermes_cli/platforms.py`` / ``hermes_cli/status.py`` — static
  PLATFORMS / status dict entries; Teams adds neither.
- ``hermes_cli/gateway.py`` — ``_PLATFORMS`` setup wizard data block
  (~33 LOC) moved into the plugin as ``interactive_setup()`` registered
  via ``setup_fn`` — same pattern Teams uses.
- ``agent/redact.py``, ``gateway/channel_directory.py``,
  ``tools/cronjob_tools.py``, ``AGENTS.md``, ``README.md`` — generic
  improvements / project listing touches reverted to keep PR scope
  tight; can resubmit as separate cleanup PRs if motivated.

cli-config.yaml.example: toolset name corrected from ``hermes-google-chat``
(hyphen, never resolved) to ``hermes-google_chat`` (underscore — matches
the ``f"hermes-{platform}"`` fallback that triggers
resolve_toolset's plugin auto-generation).

Kept (genuinely platform-specific):
- ``gateway/config.py`` — Platform enum entry + populator block + the
  documented redundant ``_PLATFORM_CONNECTED_CHECKERS`` entry. Removing
  the enum would touch ~15 call sites; the populator wires
  GOOGLE_CHAT_HOME_CHANNEL into config.platforms which the cron
  scheduler reads; the checker satisfies
  ``test_platform_connected_checkers``.
- ``gateway/run.py`` — the ``if source.platform == Platform.GOOGLE_CHAT
  and source.user_id_alt:`` block. Operators configure
  GOOGLE_CHAT_ALLOWED_USERS with email addresses, but ``source.user_id``
  is ``users/{id}`` (Chat resource name); the bridge merges
  ``user_id_alt`` (email) into the allowlist match set so email-based
  configuration works.
- ``cron/scheduler.py`` — ``_KNOWN_DELIVERY_PLATFORMS`` /
  ``_HOME_TARGET_ENV_VARS`` entries. Teams' TEAMS_HOME_CHANNEL is
  documented but not actually wired (Teams left this gap); we keep
  google_chat home-channel cron delivery working until upstream lands
  generic plugin home_channel support.

Diff went from 27 changed files to 15. Plugin side gained
``interactive_setup()`` + a beefier ``platform_hint`` to absorb the
prompt_builder + session_context content. 130/130 google_chat tests
green; ``test_platform_connected_checkers`` and
``test_prompt_builder`` both pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
donramon77 added a commit to donramon77/hermes-agent that referenced this pull request May 4, 2026
The Chat API publishes events with one of three envelope shapes
depending on how the bot is configured:

1. Workspace Add-ons (canonical, ce-type-driven):
   {"chat": {"messagePayload": {"message": {...}, "space": {...}}}}

2. Native Chat API Pub/Sub (bot configured without the Workspace
   Add-ons wrapper — events arrive directly from the publisher):
   {"type": "MESSAGE", "message": {...}, "space": {...}}

3. Relay / flat (custom Cloud Run relay that flattens Chat events into
   top-level fields so the bot can run without GCP credentials):
   {"event_type": "MESSAGE", "sender_email": ..., "text": ...,
    "space_name": ..., "thread_name": ..., "message_name": ...}

Previously only format 1 was parsed — format 2 and 3 fell through to
the messagePayload-missing branch and dropped silently. _on_pubsub_message
now delegates to a new _extract_message_payload helper that returns
(message, space, format_name) for any of the three, synthesizing a
Chat-API-shaped message dict for format 3 so downstream code
(_dispatch_message → _build_message_event) reads all three identically.

The relay-format synthesis includes a deterministic ``users/relay-…``
sender name surrogate so dedup keys stay stable across at-least-once
redelivery (Pub/Sub may replay the same logical event).

Tests: 4 new under TestExtractMessagePayload — native format extraction,
non-MESSAGE filtering, relay synthesis assertions, unrecognized
envelope handling. 130 prior tests still pass (134 total).

Patterns adapted from PR NousResearch#17828's NousResearch#14965 baseline by @ArnarValur,
who maintained a Workspace install for several weeks and identified the
parser coverage gap.

Co-Authored-By: Solmundur <168342312+ArnarValur@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
donramon77 added a commit to donramon77/hermes-agent that referenced this pull request May 4, 2026
…stry

Three reliability/operability improvements pulled from PR NousResearch#14965:

1. Outbound retry with exponential backoff. Wraps `_create_message`'s
   `messages.create().execute()` in `_call_with_retry`, a reusable
   helper that retries 3x on 429/5xx/timeout/connection errors with 1s
   → 8s base + 30% jitter. Permanent errors (4xx other than 429, auth,
   programmer errors) bubble up on the first attempt — no point
   masking misconfiguration with retries.

   Without this wrapper, a single 503 from Google's Chat REST API
   drops the user-visible reply silently.

2. Application Default Credentials fallback. `_load_sa_credentials`
   now tries `google.auth.default()` when no SA JSON is configured,
   so deploys on Cloud Run / GCE / GKE with workload identity work
   without managing key files. Local users keep using the explicit SA
   JSON path; only the unconfigured case changes (used to error,
   now ADCs).

3. Env-var registry entries for `GOOGLE_CHAT_PROJECT_ID`,
   `_SUBSCRIPTION_NAME`, `_SERVICE_ACCOUNT_JSON`, `_ALLOWED_USERS`,
   `_HOME_CHANNEL` in `hermes_cli/config.py`. Makes the env vars
   discoverable in `hermes config` UI under the "messaging" category,
   matching how Slack/Telegram/Mattermost are exposed.

Tests: 6 new (3 retry, 1 retryable-classifier, 2 ADC). 134 prior
tests still pass (140 total).

Patterns adapted from PR NousResearch#17828's NousResearch#14965 baseline by @ArnarValur.

Co-Authored-By: Solmundur <168342312+ArnarValur@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nickdlkk pushed a commit to nickdlkk/hermes-agent that referenced this pull request May 11, 2026
…NousResearch#17836)

Three narrow fixes targeting the remaining red checks after NousResearch#17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…NousResearch#17836)

Three narrow fixes targeting the remaining red checks after NousResearch#17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…NousResearch#17836)

Three narrow fixes targeting the remaining red checks after NousResearch#17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
dannyJ848 pushed a commit to dannyJ848/hermes-agent that referenced this pull request May 17, 2026
…NousResearch#17836)

Three narrow fixes targeting the remaining red checks after NousResearch#17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…NousResearch#17836)

Three narrow fixes targeting the remaining red checks after NousResearch#17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
…NousResearch#17836)

Three narrow fixes targeting the remaining red checks after NousResearch#17828:

1. ui-tui/src/app/slash/commands/ops.ts (Docker Build):
   /reload-mcp's local params type annotated session_id: string
   while ctx.sid is string | null. Widen to string | null —
   matches every other rpc call site and the test harness which passes
   { session_id: null }. Fixes TS2322 on line 86. The rpc signature
   itself is Record<string, unknown>, so this is purely a local
   typing fix, no behavioral change.

2. tests/plugins/test_achievements_plugin.py (13 cascading test failures):
   _install_fake_session_db did a raw sys.modules['hermes_state'] =
   fake_module without restoration, leaking the fake across xdist
   worker boundaries. Downstream tests doing from hermes_state import
   SessionDB got a module whose SessionDB was lambda: fake_db
   — 6 test_hermes_state.py tests failed with AttributeError: 'function'
   object has no attribute '_sanitize_fts5_query' / _contains_cjk,
   and 7 test_860_dedup.py tests failed with TypeError: got unexpected
   keyword argument 'db_path' (real code calls SessionDB(db_path=...)).

   Fix: stash monkeypatch on the plugin_api module object in the
   fixture, and have the helper do monkeypatch.setitem(sys.modules,
   'hermes_state', fake_module) for auto-restoration at test teardown.

3. tests/hermes_cli/test_web_server.py (WS race):
   TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers hit the
   30s test timeout on CI. websocket_connect returns after
   ws.accept() — but /api/events registers the subscriber in
   _event_channels on the NEXT await (inside _event_lock). A
   publish immediately after connect could race ahead of registration
   and be dropped, and the subsequent receive_text() blocked until
   SIGALRM killed the test. Fix: poll _event_channels after the
   subscriber connects, before publishing.

Validation:
scripts/run_tests.sh tests/plugins/test_achievements_plugin.py
                     tests/run_agent/test_860_dedup.py
                     tests/test_hermes_state.py
                     tests/hermes_cli/test_web_server.py    338 passed
cd ui-tui && npm run type-check                             clean
cd ui-tui && npm run build                                  clean

Remaining red checks are pure infra (Nix ubuntu hits
TwirpErrorResponse ResourceExhausted on the GH Actions cache API; Nix
macos bounces between npm build openssl-legacy and cache rate-limits)
and cannot be fixed in the codebase.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants