文件最后提交记录最后更新时间
fix(gateway): keep QQBot reconnect loop alive 21 天前
refactor(plugins): add apply_yaml_config_fn registry hook Lets platform plugins own their YAML→env config bridge instead of forcing core gateway/config.py to know every platform's schema. The hook receives the full parsed config.yaml and the platform's own sub-dict, may mutate os.environ (env > YAML precedence preserved via the standard not os.getenv(...) guards), and may return a dict to merge into PlatformConfig.extra. It runs during load_gateway_config() after the existing generic shared-key loop and before _apply_env_overrides(), mirroring the env_enablement_fn dispatch pattern (#21306, #21331). Pure addition — no behavior change for existing platforms. Each of the eight platforms with hardcoded YAML→env blocks today (discord, telegram, whatsapp, slack, dingtalk, mattermost, matrix, feishu, ~252 LOC in gateway/config.py) can migrate in independent follow-up PRs; the hardcoded blocks remain functional in the meantime, and their not os.getenv(...) guards make them no-ops for any env var the hook already set. Test coverage: 10 new tests in tests/gateway/test_platform_registry.py covering field default, callable acceptance, env mutation, extras merge, both signature args, exception swallowing, missing/non-dict sections, and env > YAML precedence. Refs #3823, #24356. Closes #24836. 21 天前
perf(gateway): defer QQAdapter and YuanbaoAdapter imports via PEP 562 (#22790) gateway/platforms/__init__.py eagerly imported QQAdapter and YuanbaoAdapter at package-init time, which transitively pulled in qqbot's chunked-upload + keyboards + onboard machinery and yuanbao's websocket stack. About 84 ms wall and 23 MB RSS on every fresh process that touched anything under gateway.platforms — including `hermes chat` (via run_agent → cli's plugin discovery transitive import). Nothing in the codebase actually consumes these symbols from the package root; every real call site uses the long-form path (from gateway.platforms.qqbot import QQAdapter, from gateway.platforms.yuanbao import YuanbaoAdapter in gateway/run.py). The eager re-export was only there for convenience. Replace with a PEP 562 module-level __getattr__ that lazily imports on first attribute access. Public API stays identical: from gateway.platforms import QQAdapter keeps working but only pays the import cost when the symbol is actually touched. __dir__ preserves help() / autocomplete behavior. Measured impact (7-run medians, 9950X3D): import gateway.platforms 127 → 43 ms (-66%) 50 → 27 MB (-46%) import gateway.platforms.base 127 → 44 ms (-65%) 50 → 27 MB (-46%) import cli (full chat path) 745 → 710 ms ( -5%) 96 → 90 MB ( -6%) hermes chat -q (cold) -5 MB The per-import win is biggest because qqbot/yuanbao deps don't overlap with anything on the gateway-platforms path — full import cli already loads aiohttp/websockets transitively from other places, so the marginal CLI win is smaller than the isolated import benchmark. The gateway.platforms.base win is what matters most for long-lived gateway processes: every gateway boot saves 23 MB resident. All 144 qqbot tests pass; broader gateway suite (5132 tests) passes modulo 4 pre-existing flakes also failing on main without this change.25 天前
fix(gateway): tighten httpx keepalive and close whatsapp typing-response leak (#18451) Two mitigations for the CLOSE_WAIT accumulation reported against QQ Bot + Feishu on macOS behind Cloudflare Warp. 1. Shared httpx.Limits helper (gateway/platforms/_http_client_limits.py). Every long-lived platform adapter now constructs httpx.AsyncClient with max_keepalive_connections=10 and keepalive_expiry=2.0, vs httpx's default of unbounded keepalive pool and 5.0s expiry. On macOS/Warp the default 5s window let idle keepalive sockets sit in CLOSE_WAIT long enough for seven persistent adapters (QQ Bot, WeCom, DingTalk, Signal, BlueBubbles, WeCom-callback, plus the transient Feishu helper) to compound to the 256-fd ulimit. Tunable via HERMES_GATEWAY_HTTPX_KEEPALIVE_EXPIRY and HERMES_GATEWAY_HTTPX_MAX_KEEPALIVE env vars. 2. whatsapp.send_typing aiohttp leak. The call was 'await self._http_session.post(...)' with no 'async with' and no variable capture — the ClientResponse went out of scope unclosed, holding its TCP socket in CLOSE_WAIT until GC. Fixed by wrapping in 'async with'. This was the only bare-await aiohttp leak in the gateway/tools/plugins tree per audit; all other aiohttp sites use the context-manager pattern correctly. The underlying reporter also saw Feishu SDK (lark-oapi) connections in CLOSE_WAIT — those are inside the SDK and out of our direct control, but tightening httpx keepalive across adapters reduces the aggregate pool pressure regardless of which individual adapter leaks. 1 个月前
[agent] fix: harden api server response headers 18 天前
fix(telegram): route image documents (.png/.jpg/.webp/.gif) through vision pipeline When users send images as documents (Telegram file picker), they were rejected with "Unsupported document type" because SUPPORTED_DOCUMENT_TYPES only includes text/office formats. Add SUPPORTED_IMAGE_DOCUMENT_TYPES to base.py and handle them in telegram.py before the document check. - Add SUPPORTED_IMAGE_DOCUMENT_TYPES constant to base.py - Add MIME reverse-lookup for image types in telegram.py - Route image documents through cache_image_from_bytes + vision pipeline - Handle media groups for image documents Closes: #20128, #18620 16 天前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
fix(dingtalk): transcribe native voice notes Sibling fix to PR #28918 (Discord voice notes). DingTalk's rich-text "voice" item type is its native voice-message format, but the adapter was routing it to MessageType.AUDIO — which gateway/run.py:7605 skips for STT. The docs claim every voice-capable platform auto-transcribes, so this brings DingTalk in line. Generic audio uploads (mapped to "file" by DINGTALK_TYPE_MAPPING) are unchanged — they were already classified as DOCUMENT, not AUDIO. Adds tests/gateway/test_dingtalk.py::TestExtractMedia covering both the voice path and the audio-passthrough invariant. 15 天前
fix: handle Discord typing indicator 429 gracefully The typing indicator loop (send_typing) ran every 8s and died on any exception, including Discord 429 rate limits. Once a 429 killed the loop, the indicator never restarted — and the raw exception bounce could cascade into broader gateway instability. Changes: - Bump sleep interval from 8s to 12s (typing light lasts ~10s) - On 429: extract retry_after, log a warning, sleep the backoff, and continue the loop - On non-rate-limit errors: log debug and return (unchanged behaviour) 14 天前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
fix(async): close unscheduled coroutines in all threadsafe bridges (#26584) Wraps every sync->async coroutine-scheduling site in the codebase with a new agent.async_utils.safe_schedule_threadsafe() helper that closes the coroutine on scheduling failure (closed loop, shutdown race, etc.) instead of leaking it as 'coroutine was never awaited' RuntimeWarnings plus reference leaks. 22 production call sites migrated across the codebase: - acp_adapter/events.py, acp_adapter/permissions.py - agent/lsp/manager.py - cron/scheduler.py (media + text delivery paths) - gateway/platforms/feishu.py (5 sites, via existing _submit_on_loop helper which now delegates to safe_schedule_threadsafe) - gateway/run.py (10 sites: telegram rename, agent:step hook, status callback, interim+bg-review, clarify send, exec-approval button+text, temp-bubble cleanup, channel-directory refresh) - plugins/memory/hindsight, plugins/platforms/google_chat - tools/browser_supervisor.py (3), browser_cdp_tool.py, computer_use/cua_backend.py, slash_confirm.py - tools/environments/modal.py (_AsyncWorker) - tools/mcp_tool.py (2 + 8 _run_on_mcp_loop callers converted to factory-style so the coroutine is never constructed on a dead loop) - tui_gateway/ws.py Tests: new tests/agent/test_async_utils.py covers helper behavior under live loop, dead loop, None loop, and scheduling exceptions. Regression tests added at three PR-original sites (acp events, acp permissions, mcp loop runner) mirroring contributor's intent. Live-tested end-to-end: - Helper stress test: 1500 schedules across live/dead/race scenarios, zero leaked coroutines - Race exercised: 5000 schedules with loop killed mid-flight, 100 ok / 4900 None returns, zero leaks - hermes chat -q with terminal tool call (exercises step_callback bridge) - MCP probe against failing subprocess servers + factory path - Real gateway daemon boot + SIGINT shutdown across multiple platform adapter inits - WSTransport 100 live + 50 dead-loop writes - Cron delivery path live + dead loop Salvages PR #2657 — adopts contributor's intent over a much wider site list and a single centralized helper instead of inline try/except at each site. 3 of the original PR's 6 sites no longer exist on main (environments/patches.py deleted, DingTalk refactored to native async); the equivalent fix lives in tools/environments/modal.py instead. Co-authored-by: JithendraNara <jithendranaidunara@gmail.com>19 天前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
chore: ruff auto-fix C401, C416, C408, PLR1722 (#23940) C401: set(x for x in y) -> {x for x in y} (set comprehension) C416: [(k,v) for k,v in d] -> list(d.items()) (unnecessary listcomp) C408: tuple()/dict() -> ()/{} (unnecessary collection call) PLR1722: exit() -> sys.exit() (adds import sys where needed) 21 instances fixed, 0 remaining. 19 files, +40/-36.23 天前
fix(gateway): preserve underscores in plain-text identifiers 18 天前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
fix(matrix): implement thread_require_mention to prevent multi-agent reply loops In multi-agent shared Matrix rooms, multiple bots all participating in the same thread could trigger infinite reply loops — each bot's reply re-engaged the others because they were all in the bot-thread set. Discord has a thread_require_mention opt-in for this; Matrix didn't. Add _parse_thread_require_mention(config) (mirrors Discord's pattern). In _resolve_message_context, when enabled and the message is in a bot-participated thread (not a free-response room), require @mention before processing. Salvage of @justemu's 2-commit stack (#27996). Fixes #27995. 16 天前
fix(mattermost): resolve thread root_id and route progress to threads Two Mattermost thread-related bugs: 1. _resolve_root_id() — Mattermost CRT requires root_id to be the thread root post. Using any reply's own ID as root_id causes '400 Invalid RootId'. Add _resolve_root_id() that walks up the post chain via API to find the actual root, and apply it in send(), _send_url_as_file(), and _send_local_file(). 2. _progress_reply_to — The condition in run.py only checked Platform.FEISHU, missing Mattermost entirely. This caused tool progress messages to always land in the main channel instead of the thread. Add Platform.MATTERMOST to the condition so progress messages are routed to threads when reply_mode=thread. Impact: Tool progress messages now appear in Mattermost threads instead of flooding the main channel; thread replies no longer fail with Invalid RootId when the reply target is itself a reply. 16 天前
fix(msgraph_webhook): harden auth surface + IP allowlisting + response hygiene Defense-in-depth polish on top of the webhook listener before it becomes a real attack surface once the pipeline starts creating subscriptions and Graph starts POSTing to the configured public URL. - Timing-safe clientState comparison. Previously used == on strings; switches to hmac.compare_digest so a mismatch does not leak how many leading characters matched. client_state is documented as a strong shared secret (openssl rand -hex 32 in the setup docs), so a timing-safe primitive is the right call. - Split GET and POST handlers. Graph validates a subscription by sending GET with validationToken in the query; anything else on GET is now a 400 so the endpoint cannot be probed or mistakenly used for data exfil. Previously a bare GET fell through to the POST path and blew up on request.json() with a confusing 400. - Empty response bodies on success. 202 is returned with no body so internal counters (accepted / duplicates / scheduled) do not leak to any caller that can reach the endpoint; counters remain observable via /health for operators. 403 on every-item-bad-clientState batches (so forged POSTs stop retrying), 400 on malformed / unknown-resource batches (sender configuration issue). - Optional source-IP allowlist. New allowed_source_cidrs extra field (list or comma-separated string) and MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS env var let operators restrict the webhook to Microsoft Graph's published webhook source ranges in production. Empty = allow all, preserving dev-tunnel / localhost workflows. Invalid CIDRs are logged and ignored rather than crashing. Also gates the handshake endpoint so disallowed IPs cannot probe it. - Tests updated for the new response contract (empty-body 202, auth-only 403, config-error 400) and extended to cover: bare GET rejection, POST-with-validationToken handshake tolerance, timing-safe compare actually invoked via hmac.compare_digest spy, malformed body / missing value array, IP allowlist accept/reject paths, handshake IP allowlist, invalid CIDR entries, comma-string CIDR list parsing. 52/52 passed (was 40). Full gateway suite: 5049 passed / 1 pre-existing failure in test_discord_free_response (unrelated, reproduces on clean origin/main). 26 天前
feat(signal): add require_mention filter for group chats Add a configurable mention filter to the Signal adapter so the bot only responds in groups when it is explicitly @mentioned. Changes: - gateway/platforms/signal.py: read require_mention from adapter extra config or SIGNAL_REQUIRE_MENTION env var; skip group messages that don't mention the bot account (checked in rendered text and raw mention metadata) - gateway/config.py: map signal.require_mention YAML key to the SIGNAL_REQUIRE_MENTION env var (env var takes precedence) Config example: signal: require_mention: true Or via env var: SIGNAL_REQUIRE_MENTION=true 16 天前
feat(gateway/signal): add support for multiple images sending Adds a new send_multiple_images method to the BasePlatformAdapter that implements the default "One image per message" loop and allows for platform-specific overriding. Implements such an override for the Signal adapter, batching images and trying (best-effort) to work around rate-limits for voluminous batches using a specific scheduler. Also implements batching + rate-limit handling in the send_message tool. New tests added for the Signal adapter, its rate-limit scheduler and the send_message tool 1 个月前
fix(gateway): add trust_env=True to aiohttp sessions in SMS, Slack, Teams, Google Chat adapters aiohttp.ClientSession defaults to trust_env=False, which silently ignores HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY environment variables. Users behind a corporate or network proxy cannot reach external APIs on any of these platforms — all outbound requests fail with connection errors. Symmetric with wecom.py (line 276), weixin.py (lines 1055/1268/1274), and matrix.py (no-proxy path) which already set this flag. Complements the open LINE fix (#26635) with the remaining gateway and plugin adapters. Changed: - gateway/platforms/sms.py: persistent Twilio session (connect) + fallback session (send) — both hit https://api.twilio.com - gateway/platforms/slack.py: ephemeral response_url POST session — hits https://hooks.slack.com/... callback URLs - plugins/platforms/teams/adapter.py: standalone send session — hits login.microsoftonline.com (token) + Bot Framework service URL - plugins/platforms/google_chat/adapter.py: standalone send session — hits https://chat.googleapis.com/v1/... WhatsApp sessions are excluded: they connect to http://127.0.0.1:{port} (local bridge) and must not be routed through a system proxy. 18 天前
fix(gateway): add trust_env=True to aiohttp sessions in SMS, Slack, Teams, Google Chat adapters aiohttp.ClientSession defaults to trust_env=False, which silently ignores HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY environment variables. Users behind a corporate or network proxy cannot reach external APIs on any of these platforms — all outbound requests fail with connection errors. Symmetric with wecom.py (line 276), weixin.py (lines 1055/1268/1274), and matrix.py (no-proxy path) which already set this flag. Complements the open LINE fix (#26635) with the remaining gateway and plugin adapters. Changed: - gateway/platforms/sms.py: persistent Twilio session (connect) + fallback session (send) — both hit https://api.twilio.com - gateway/platforms/slack.py: ephemeral response_url POST session — hits https://hooks.slack.com/... callback URLs - plugins/platforms/teams/adapter.py: standalone send session — hits login.microsoftonline.com (token) + Bot Framework service URL - plugins/platforms/google_chat/adapter.py: standalone send session — hits https://chat.googleapis.com/v1/... WhatsApp sessions are excluded: they connect to http://127.0.0.1:{port} (local bridge) and must not be routed through a system proxy. 18 天前
fix(gateway): extend observe+attribution to location and media handlers _handle_location_message and _handle_media_message were skipped when the observe-unmentioned-group-messages feature landed (a9db0e2c7). Both handlers now: 1. Check _should_observe_unmentioned_group_message on the skipped path and call _observe_unmentioned_group_message so group chatter is stored as shared session context even when the bot is not addressed. 2. Call _apply_telegram_group_observe_attribution on the triggered path so the dispatched event uses the shared (user_id=None) group session instead of the per-user session, letting the model see previously observed context. For stickers the attribution is applied after _handle_sticker completes (which overwrites event.text with the vision description); for all other media types it is applied once after caption cleaning. Four new tests cover the observe and attribution paths for both handlers. 14 天前
fix(telegram): reset sticky fallback IP on connect failure, retry primary DNS When a sticky fallback IP (from DoH discovery) becomes unreachable, the transport previously got stuck in an attempt_order that only tried the dead IP. This prevented the gateway from recovering until the service was restarted. Changes: - Always include primary DNS path (None) after the sticky IP in the attempt_order so that a primary-path retry happens on sticky failure. - Reset self._sticky_ip to None when the currently sticky IP hits a connect timeout / connect error, allowing the next request to retry from scratch. Fixes silent Telegram disconnection when discovered fallback IPs are transiently or permanently unreachable. 16 天前
fix: remove unused import and hoist module-level constant - Remove unused from tools/tts_tool.py (dead code) - Move _BUILTIN_DELIVER_PLATFORMS set from send() method to module scope in gateway/platforms/webhook.py to avoid reallocation on every call 18 天前
fix(wecom): handle WSMsgType.CLOSING to prevent CPU spin The WeCom adapter's _read_events() loop only handled CLOSE, CLOSED, and ERROR websocket message types. When the server initiates a graceful shutdown, aiohttp returns WSMsgType.CLOSING before the connection is fully closed. This message type was not handled, causing the receive() call to return immediately in a tight loop while self._ws.closed remained False. The result was 100% CPU usage on the asyncio event loop. Add WSMsgType.CLOSING to the set of terminal message types that raise RuntimeError("WeCom websocket closed"), allowing _listen_loop() to enter its normal reconnect backoff path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> 16 天前
fix(gateway): tighten httpx keepalive and close whatsapp typing-response leak (#18451) Two mitigations for the CLOSE_WAIT accumulation reported against QQ Bot + Feishu on macOS behind Cloudflare Warp. 1. Shared httpx.Limits helper (gateway/platforms/_http_client_limits.py). Every long-lived platform adapter now constructs httpx.AsyncClient with max_keepalive_connections=10 and keepalive_expiry=2.0, vs httpx's default of unbounded keepalive pool and 5.0s expiry. On macOS/Warp the default 5s window let idle keepalive sockets sit in CLOSE_WAIT long enough for seven persistent adapters (QQ Bot, WeCom, DingTalk, Signal, BlueBubbles, WeCom-callback, plus the transient Feishu helper) to compound to the 256-fd ulimit. Tunable via HERMES_GATEWAY_HTTPX_KEEPALIVE_EXPIRY and HERMES_GATEWAY_HTTPX_MAX_KEEPALIVE env vars. 2. whatsapp.send_typing aiohttp leak. The call was 'await self._http_session.post(...)' with no 'async with' and no variable capture — the ClientResponse went out of scope unclosed, holding its TCP socket in CLOSE_WAIT until GC. Fixed by wrapping in 'async with'. This was the only bare-await aiohttp leak in the gateway/tools/plugins tree per audit; all other aiohttp sites use the context-manager pattern correctly. The underlying reporter also saw Feishu SDK (lark-oapi) connections in CLOSE_WAIT — those are inside the SDK and out of our direct control, but tightening httpx keepalive across adapters reduces the aggregate pool pressure regardless of which individual adapter leaks. 1 个月前
feat(gateway): add WeCom callback-mode adapter for self-built apps Add a second WeCom integration mode for regular enterprise self-built applications. Unlike the existing bot/websocket adapter (wecom.py), this handles WeCom's standard callback flow: WeCom POSTs encrypted XML to an HTTP endpoint, the adapter decrypts, queues for the agent, and immediately acknowledges. The agent's reply is delivered proactively via the message/send API. Key design choice: always acknowledge immediately and use proactive send — agent sessions take 3-30 minutes, so the 5-second inline reply window is never useful. The original PR's Future/pending-reply machinery was removed in favour of this simpler architecture. Features: - AES-CBC encrypt/decrypt (BizMsgCrypt-compatible) - Multi-app routing scoped by corp_id:user_id - Legacy bare user_id fallback for backward compat - Access-token management with auto-refresh - WECOM_CALLBACK_* env var overrides - Port-in-use pre-check before binding - Health endpoint at /health Salvaged from PR #7774 by @chqchshj. Simplified by removing the inline reply Future system and fixing: secrets.choice for nonce generation, immediate plain-text acknowledgment (not encrypted XML containing 'success'), and initial token refresh error handling. 1 个月前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
fix(gateway): keep running when platforms fail; add per-platform circuit breaker + /platform (#26600) Stop the gateway from exiting (or systemd-restart-looping) when a single messaging adapter fails at startup or runtime. A misconfigured WhatsApp (npm install timeout, unpaired bridge, missing creds.json) used to take the entire gateway down, killing cron jobs and any other connected platforms with it. Changes: • Startup (gateway/run.py): when connected_count==0 but the only errors are retryable, log a degraded-state warning and keep the gateway alive instead of returning False. Reconnect watcher then recovers platforms as their underlying problem clears. • Runtime (gateway/run.py _handle_adapter_fatal_error): when the last adapter goes down with a retryable error and is queued for reconnection, stay alive instead of exit-with-failure. Previously this triggered systemd Restart=on-failure, which created infinite restart loops on persistent retryable failures (proxy outage, repeated bridge crashes). • Reconnect watcher (gateway/run.py _platform_reconnect_watcher): replace the 20-attempt hard drop with a circuit-breaker pause. After _PAUSE_AFTER_FAILURES (10) consecutive retryable failures, the platform stays in _failed_platforms with paused=True so the watcher skips it but the operator can still see and resume it. Non-retryable errors still drop out of the queue immediately. Resolves #17063 (gateway giving up on Telegram after 20 attempts). • WhatsApp preflight (gateway/platforms/whatsapp.py): refuse to start the Node bridge when creds.json is missing. Sets a non-retryable whatsapp_not_paired fatal error so the watcher drops it cleanly with a single 'run hermes whatsapp' log line instead of paying the 30s bridge bootstrap timeout on every gateway start. • WhatsApp setup ordering (hermes_cli/main.py cmd_whatsapp): only set WHATSAPP_ENABLED=true once pairing actually succeeds. Previously the wizard wrote the env var at step 2 (before npm install and QR pairing), so any Ctrl+C left .env claiming WhatsApp was ready when the bridge had no creds.json. Also propagate the env var when the user keeps an existing pairing on a re-run. • /platform slash command (hermes_cli/commands.py + gateway/run.py): new gateway-only command for manual circuit-breaker control. /platform list — show connected + failed/paused platforms /platform pause <name> — silence a known-broken platform /platform resume <name> — re-queue a paused platform Tests: • New: pause/resume helpers, /platform list|pause|resume command, WhatsApp creds.json preflight, WhatsApp setup ordering. • Updated: stale assertions that codified the old 'exit and let systemd restart' behavior in test_runner_fatal_adapter.py, test_runner_startup_failures.py, and test_platform_reconnect.py (the 20-attempt give-up test became a circuit-breaker pause test). 5488 tests pass in tests/gateway/.19 天前
feat(state.db): persist platform_message_id; restore yuanbao exact-id recall PR #29211 dropped JSONL gateway transcripts and noted that the platform's own message_id field (used by Yuanbao's recall guard to redact a message by exact platform id) was no longer preserved — falling back to content-match. That fallback works for the common case but redacts the wrong row when two messages share text (or fails to match when content is post-processed). Restore exact-id matching by giving state.db a column for it: - New platform_message_id TEXT column on the messages table (SCHEMA_VERSION bump 11 → 12; column added via declarative reconciler on existing DBs, no version-gated migration block needed) - Partial index idx_messages_platform_msg_id on (session_id, platform_message_id) to keep recall's point-lookup cheap even on large sessions - append_message() and replace_messages() accept the new value: the gateway-facing append_to_transcript in gateway/session.py forwards either message["platform_message_id"] or the legacy message["message_id"] key (yuanbao's existing convention) - get_messages_as_conversation() surfaces the column back on the message dict as message_id so platform code reads the same shape it used to read from JSONL - Yuanbao _patch_transcript: restore branch A1 (exact id match) ahead of A2 (content match) ahead of B (system-note). Both branches log which one fired so operators can tell from gateway.log whether recall hit the canonical path or had to fall back. Tests: - New low-level round-trip tests in test_hermes_state.py for both append_message and replace_messages paths - The PR's test_yuanbao_recall_db_only.py was rewritten to assert the new contract: branch A1 (id match) works against DB-only transcripts, and branch A2 (content match) still recovers rows that were observed without a platform id (e.g. agent-processed @bot messages where run.py doesn't carry msg_id through) 14 天前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).23 天前
yuanbao platform (#16298) Co-authored-by: loongzhao <loongzhao@tencent.com>1 个月前