Commit Graph
9 Commits
Author SHA1 Message Date
Abiba (pi) 870f64d8a9 fix(zulip): edit_message accepts **kwargs, returns SendResult; use POST for edits
CI / validate (pull_request) Failing after 2s
The Gateway streaming interface calls edit_message() with finalize=True
and expects SendResult. Fixed:
1. Added **kwargs to edit_message() signature — silently ignores finalize
2. Changed return type from bool to SendResult for Gateway compatibility
3. Switched from PATCH to POST /api/v1/messages/{id} — Zulip docs use
   POST for message editing (PATCH returns 405 Method Not Allowed)
4. Same fixes applied to delete_message() and send()

Discovered during Tanko Gen 4 testing.
2026-06-27 14:18:42 +00:00
Abiba (pi) 48bc66b42f feat(zulip): Gen 4 — connection pool recovery, heartbeat logging, log sanitization
CI / validate (pull_request) Failing after 6s
Fixes 4 critical reliability issues discovered during Zulip server outage recovery:

1. CLOSE-WAIT socket leak: _reconnect() now creates a fresh httpx.AsyncClient()
   when the queue expires, preventing stuck connection pool. Old client is
   explicitly aclose()'d.

2. Silent poll loop death: After sustained 502 errors, the poll loop was
   returning [] without logging — transport errors are swallowed by _api_call
   returning (None, 0). Added heartbeat logging every 5 minutes and silence
   detection warnings after 60s of no events.

3. HTTP client stuck pool: Added proactive client recreation after 20 consecutive
   empty polls (connection pool exhaustion detection) and periodic refresh every
   500 polls (connection reuse limit).

4. Log spam: 502 error responses from Netbird contain full HTML pages — now
   truncated to 80 chars with newlines stripped.

Also:
- Support ZULIP_URL env var (in addition to ZULIP_SITE)
- expose silence_seconds, consecutive_empty_polls, client_pool_resets in health stats
- Reduced health callback interval from 600s to 300s
2026-06-27 14:12:52 +00:00
Abiba (pi) 6bb438de6e fix(zulip): remove invalid kwargs from SendResult calls
SendResult only accepts: success, message_id, error, raw_response,
retryable. The adapter was passing platform=, chat_id=, and metadata=
which are not valid fields, causing TypeError on every send attempt.

This was the error Tanko was showing in Zulip:
'SendResult.__init__() got an unexpected keyword argument "platform"'
2026-06-27 11:34:14 +00:00
Abiba (pi) b1bef9024f fix(zulip): resolve bot user_id from /users/me for @mention detection
The /register endpoint doesn't always return user_id on all Zulip
server versions. Added _resolve_bot_user_id() that queries the
/users/me endpoint as a fallback. This ensures @mention detection
(is_direct_mention check) works even when register doesn't provide it.

Also bumped to v1.0.3 — cumulative fixes since v1.0.1:
- v1.0.2: BAD_EVENT_QUEUE_ID detection (silent queue expiry)
- v1.0.3: bot user_id resolution (@mention detection)
2026-06-27 05:51:12 +00:00
Abiba (pi) dcf2de0052 fix(zulip): detect BAD_EVENT_QUEUE_ID instead of silently swallowing it
Root cause of Tanko not responding to DMs after initial connection:
Zulip event queues expire after ~10min of dont_block polling. When the
queue expired, _api_call() returned None for all 400-level errors, which
_fetch_events() treated as 'no events' and returned []. The poll loop
never knew the queue died, so it never reconnected.

Fix:
1. _api_call() now returns (Optional[Dict], status_code) tuple
2. _fetch_events() explicitly checks for 400 status and raises
   RuntimeError('BAD_EVENT_QUEUE_ID') for the poll loop to catch
3. All callers updated to unpack the tuple

Now when a queue expires, the poll loop catches the error and
calls _reconnect() to register a fresh queue.
2026-06-27 05:50:13 +00:00
Abiba (pi) 715d54564f fix(zulip): pass Platform enum instead of string to BasePlatformAdapter.__init__
Root cause of 'str' object has no attribute 'value' error during connect():
the adapter passed a raw string 'zulip' to the base class's platform
parameter, which expects a Platform enum instance. When self.name was
accessed (calling self.platform.value.title()), the string had no .value
attribute.

Fix: import Platform from gateway.config and wrap with Platform('zulip').
2026-06-27 05:33:46 +00:00
Abiba (pi) 0acf1068bf feat(hermes): Zulip adapter Gen 3 — malformed message resilience, periodic @all-bots refresh, health callback
Gen 3 improvements from build-zulip-plugin contract run:

1. Malformed message resilience
   - Per-event try/except in poll loop — one bad event never kills adapter
   - malformed_events counter tracked in health stats
   - Fulfills Success Criterion: handles_malformed_messages == true

2. Periodic @all-bots refresh
   - _all_bots_refresh_forever() background task re-resolves every hour
   - Cancelled gracefully on disconnect()
   - all_bots_refreshes counter in health stats

3. Health stats callback mechanism
   - set_health_callback(callback, interval=600) — register external consumer
   - _report_health_if_callback() triggers on dedup cleanup cycle
   - Designed for Hermes Gateway to wire to RA-H OS knowledge graph logging
   - Cross-contract integration: get_all_bots_user_id() and get_bot_user_id()
     exposed for zulip-mention-reliability contract
2026-06-26 22:31:16 +00:00
Abiba (pi) 1c8f48fd77 feat(hermes): Zulip adapter Gen 2 — dedup cleanup task, self-test, health stats, dynamic all-bots
Gen 2 improvements from build-zulip-plugin contract run:

1. Background dedup maintenance task
   - _dedup_cleanup_forever() runs every 120s, pruning stale entries
   - _is_duplicate() is now pure O(1) — no per-call cleanup overhead
   - Cleanup is cancelled gracefully on disconnect

2. Self-test diagnostics (selftest())
   - 8 checks: connection, queue, HTTP client, bot identity, poll loop,
     dedup cleanup, echo prevention, @all-bots configuration
   - Returns structured verdict: healthy / degraded / critical_failure
   - Verifies every Success Criterion from the contract

3. Health stats tracking (get_health_stats())
   - Uptime, poll counts/errors, message routing counts, send stats
   - Error rate computation, dedup map size
   - Suitable for periodic RA-H OS knowledge graph logging

4. Dynamic @all-bots resolution
   - _resolve_all_bots_user_id() queries Zulip /api/v1/users on connect
   - Falls back to configured env var or hardcoded default (1)
   - Eliminates fragile hardcoded default in multi-bot deployments

5. Connection lifecycle now manages both poll_task and dedup_cleanup_task
   - Both cancelled gracefully on disconnect()
   - dedup task starts in connect() after queue registration
2026-06-26 22:23:38 +00:00
Abiba (pi) 2ee8a4d818 feat: Hermes Zulip native platform plugin — deprecates old standalone service
CI / validate (pull_request) Failing after 2s
Builds on the NousResearch Hermes Agent plugin system:

- plugins/platforms/zulip/ — full BasePlatformAdapter implementation
  - Zulip event queue polling (like pi-zulip-extension)
  - DM-first + @mention + @all-bots routing
  - Placeholder→edit streaming UX
  - Typing indicators
  - Deduplication and echo-loop prevention
  - Auto-registers via register(ctx) — zero core changes
  - plugin.yaml with full env var schema
- hermes-zulip-plugin/DEPRECATED.md — migration guide from old subprocess-based service

Configuration:
  Env vars: ZULIP_SITE, ZULIP_EMAIL, ZULIP_API_KEY (required)
  Config: platforms.zulip.extra in Hermes config.yaml
2026-06-25 21:22:52 +00:00