feat(teams): Microsoft Teams platform adapter as a plugin (+ xdist collision guard)#17828
Merged
Conversation
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.
5 tasks
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.
5 tasks
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.
4 tasks
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 implementationplugins/platforms/teams/plugin.yaml,__init__.py— plugin wiringtests/gateway/test_teams.py— test suitetools_config.py— handle plugin platforms inplatform_tool_universe.env.example,docker-compose.yml,cli-config.yaml.exampleupdateswebsite/docs/user-guide/messaging/teams.mddocsMy follow-up commit
1. Fix the root cause of the CI test failure
test_teams.pyandtest_irc_adapter.py(already on main) both did:Every plugin ships its own
adapter.py, so the bareimport adapterraces forsys.modules['adapter']. Whichever collects first in an xdist worker wins; the other crashes with ImportError, and the pollutedsys.pathcascades 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'sadapter.pyviaimportlib.util.spec_from_file_locationunder a unique module name (plugin_adapter_<name>). Zerosys.pathmutation.3. Both plugin tests migrated to the helper
Removed all
sys.path.inserttricks and the 7 in-methodfrom adapter import registercalls.4. Anti-pattern guard (tests/gateway/conftest.py)
New
pytest_configurehook (runs on xdist controller before workers spawn) AST-scans everytest_*.pyundertests/gateway/and fails the session with a clear remediation message if it finds:sys.path.insert/append/extend(...)pointing intoplugins/platforms/(catches both string-literal andPath(...) / 'plugins' / 'platforms'forms), ORimport 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.com→heyitsaamirto AUTHOR_MAP so check-attribution passes.Validation
tests/gateway/suitetest_teams.py+test_irc_adapter.pyboth orderingssys.path+plugins/platforms+ barefrom adapter"/…/plugins/platforms/…"Closes
Supersedes #17764. Original authorship preserved via rebase-merge.