feat(line): signature enforcement, DISCORD quality, state_lock, tests, doc/ci fixes - #78291
Open
YoshitsuguOtani wants to merge 39 commits into
Open
feat(line): signature enforcement, DISCORD quality, state_lock, tests, doc/ci fixes#78291YoshitsuguOtani wants to merge 39 commits into
YoshitsuguOtani wants to merge 39 commits into
Conversation
Add LineAdapter with webhook HTTP server (aiohttp), signature verification, reply/push APIs, dedup, retry + dead-letter queue. Includes 56 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract embed text (title/description/fields) from webhook bot messages - Extract embed image URLs (embed.image.url / embed.thumbnail.url) into media_urls - Disable OrbStack proxy injection in docker-compose.yml (fixes Discord/external API connectivity) - Add NO_PROXY IPv6 CIDR sanitization (fixes httpx parse errors) Frigate → Discord webhook → Hermes Bot auto-analysis flow now working end-to-end. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add list-type prompt support in resolve_channel_prompt() with empty-string filtering to handle edge cases safely - Implement random personality selection from prompt lists for Discord/Frigate notifications - Change LINE group_policy default from "disabled" to "open" so group chats work out of the box (overridable via LINE_GROUP_POLICY) - Normalize docker-compose project name to "15-hermes" and gateway container name to "hermes-gateway" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auto-merged cleanly: no conflicts across 200+ files - line.py preserved (feat/line-adapter addition, not in main) - discord.py: merged lazy-deps installation with random personality selection - base.py: merged upstream changes with list prompt filter - Includes upstream improvements: shutdown forensics, slash access control, stream consumer enhancements, Telegram adapter rewrite, new platform adapters, and more Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- get_chat_info: route by LINE ID prefix (C=group, R=room, U=dm)
and call the correct API endpoint for each chat type
- Resolve sender display names in group/room via LINE member API
(/group/{id}/member/{uid} or /room/{id}/member/{uid})
- Add TTL-based member name cache (1h) with periodic cleanup
- Register LINE in all four Gateway allowlist maps
(platform_env_map, allow_all, group_user, group_chat)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… member names - Add _MEMBER_NAME_CACHE_MAX_SIZE (500) with oldest-eviction (25%) to prevent unbounded memory growth in large groups - Call _cleanup_member_name_cache() in _send_loop so stale entries are purged even when only push messages are sent - Add _evict_oldest_if_needed() triggered on each cache insertion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent webhook handler from blocking up to the aiohttp default (5 min) when LINE's member API is unresponsive. Falls back to user_id when the lookup times out. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upstream moved LINE from built-in gateway to plugin, but our branch retains the built-in adapter with group chat support. Manual resolution confirmed: gateway/platforms/line.py (856 lines), Platform.LINE allowlist entries, and Platform.LINE enum all preserved. Upstream changes included: - fix(tui): fast-echo bypass for Vietnamese/CJK/IME input - fix(tui): background process completion notifications - fix(langfuse): complete observability fix - chore(release): map contributor email Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add TestGetChatInfo (6 tests): prefix-based routing for group/room/dm with API fallback and edge cases (empty chat_id, unknown prefix) - Add TestMemberDisplayName (8 tests): cache hit/miss, TTL expiry, API timeout, no-display-name response, dm/noop - Add TestMemberNameCache (4 tests): max-size eviction, TTL cleanup, disconnect clear - Fix 2 pre-existing tests: update group_policy default from 'disabled' to 'open' (matches current LineAdapter default) - Clear _member_name_cache on disconnect() for resource hygiene Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ution Verify end-to-end in TestGroupMessageUserName: - Group webhook events resolve sender displayName via member API - Room webhook events resolve sender displayName via member API - DM webhook events use user_id as name (no API call) - API failure falls back to user_id gracefully - Second message from same member hits cache (no extra API call) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n, cache TTL, and cleanup
- get_chat_info: branch on chat_id prefix (U=user, C=group, R=room) to call
the correct LINE API instead of always hitting /profile
- _get_member_display_name: resolve sender display names in group/room chats
via /group/{id}/member/{uid} and /room/{id}/member/{uid}
- _member_name_cache: TTL-based eviction (1h) and max-size cap (500) to
prevent unbounded growth and stale entries
- _cleanup_loop: periodic task that prunes expired caches, stale retry
counts, and dead-letter queue overflow
- _retry_counts: add per-entry timestamps for TTL-based cleanup
- _dead_letter_queue: cap at 1000 entries, prune to 100 when exceeded
- line_api_get/line_api_post: add aiohttp.ClientTimeout(30s), JSON parse
guards, and log improvements
- docker-compose.yml: add all LINE env vars, rename containers (gateway,
dashboard), add API key env vars
- tests: 94 tests covering get_chat_info branching, member display name
resolution, cache eviction, retry cleanup, DLQ capping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o LINE plugin adapter
- Add _LineClient.get_member_profile() to fetch group/room member profiles
via /v2/bot/group/{id}/member/{uid} and /v2/bot/room/{id}/member/{uid}
- Resolve sender display names in _handle_message_event for group/room chats
- Add _member_name_cache with 1h TTL and 500-entry max size (LRU eviction)
- Add _cleanup_loop: periodic task pruning expired caches every 5 min
- Add _cleanup_member_name_cache, _cleanup_retry_counts,
_prune_dead_letter_queue, _clear_expired_reply_tokens
- Wire cleanup task into connect/disconnect lifecycle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… /webhook/line) - Change DEFAULT_WEBHOOK_PORT from 8646 to 8645 and DEFAULT_WEBHOOK_PATH from /line/webhook to /webhook/line to maintain backward compatibility with the custom domain line-webhook.retrocraft.jp - Add LINE_WEBHOOK_PATH env var support to enable path overrides - Add LINE_PORT and LINE_WEBHOOK_PATH to docker-compose.yml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Docker healthcheck to gateway (curl /webhook/line/health) and dashboard (curl :9119) services with 30s interval, 3 retries - Skip LINE webhook signature verification when channel_secret is unset, enabling local / dev testing without LINE's HMAC-SHA256 signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and UX improvements - Add _retry_api_call with exponential backoff for 429/5xx + network errors - Extract _extract_line_status() for robust status-code parsing - Fix MessageType.IMAGE → PHOTO (non-text messages were crashing) - Add send-content logging on LINE API 400+ errors for diagnostics - Add group chat cooldown (LINE_GROUP_COOLDOWN, default 300s) - Add group chat random delay (LINE_GROUP_RANDOM_DELAY) - Set LINE_SLOW_RESPONSE_THRESHOLD=60s, LINE_PENDING_TEXT=AI考え中 - Enforce 200-char response limit in platform_hint Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…_call - Add 14 tests covering status code extraction and retry logic - Fix _extract_line_status to scan all parts (was looking at parts[1] which is the HTTP method, not parts[2] which is the status code) - Tests cover: 429/503 retry, 400 no-retry, aiohttp.ClientError retry, OSError retry, max_retries exhaustion, and edge cases Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rvice Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extend LINE_GROUP_COOLDOWN to accept a range "min,max" (e.g. "60,120" for random 1-2 minute cooldown per response). Each group response now picks a random duration within the range, making bot behavior less predictable and more human-like. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the soft 200-character platform_hint with a hard truncation in send(). The agent can now reason at full length while LINE users always see concise ≤200 char responses. System bypass messages and postback cache entries are not truncated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DMs (U-prefixed chat_id) now receive the full response. Truncation applies only to groups (C) and rooms (R) to keep multi-user chats readable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add DISCORD_FORWARD_TO_LINE env var to relay camera alert embeds from Discord channels to LINE chats. Downloads the snapshot image, re-serves it via LINE_PUBLIC_URL, and pushes a compact text + image notification. Fire-and-forget — LINE failures never block Discord processing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prevents repetitive tool-call messages (e.g. vision_analyze spam during Frigate camera alerts) from filling Discord channels. Only the first call of each tool type is now shown as a progress bubble. Also adds per-platform override for discord in user config to ensure the change takes effect regardless of global display.tool_progress=all. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
These directories are external projects or backup files, not part of the hermes-agent source tree. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- gateway/platforms/discord.py: - _forward_embed_to_line() に is_safe_url() チェックを追加(SSRF 対策) - resp.read() に 10MB サイズ制限を追加(OOM 対策) - image_url = None 時の TypeError を防ぐ if ガードを追加 - plugins/platforms/line/adapter.py: - template/flex メッセージタイプの受信処理を追加 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
コンテナ起動時に/usr/bin/pythonが存在しない場合、 /usr/bin/python3へのsymlinkを自動作成する。 これにより、pythonコマンドを呼び出すスクリプトの 実行エラーを防止する。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Merge後の自動ビルド・デプロイ用スクリプト。 mainブランチでの実行を前提とし、以下の処理を行う: 1. mainブランチ確認 2. git pull origin main 3. テストスイート実行(--skip-testsで省略可能) 4. Dockerイメージビルド 5. コンテナ再起動 6. ヘルスチェック オプション: --dry-run 実行せずステップを表示 --skip-tests テストをスキップ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- LINE adapter: aiohttp ClientSessionを再利用化(FD枯渇防止) - LINE adapter: disconnect()でセッションを明示的にクローズ - Discord: get_channel/fetch_channel重複を_resolve_channel()に集約 - Discord: message.embedsをgetattr安全アクセスに修正(テスト32F→67P) - url_safety: DNSキャッシュ追加(60秒TTL、ブロッキングI/O軽減) - url_safety: is_safe_url_async()追加、Discord asyncメソッドで使用 - scripts/post-merge-build.sh: eval除去 + ヘルスチェック失敗時exit 1 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove redundant `import tempfile` in _forward_embed_to_line (already imported at top) - Upgrade LINE forward debug logs to warning (download failure + push failure) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Log a WARNING when LINE webhook signature verification is skipped due to unset channel_secret, making the security trade-off visible. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- gateway/platforms/discord.py: _resolve_channel() for parent resolution - scripts/post-merge-build.sh: GATEWAY_CONTAINER_NAME env var override Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Clean up the inserted project-root path after imports complete so the mutation does not leak to other modules at runtime. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- line.py: webhook signature enforcement (default reject, LINE_SKIP_SIGNATURE_VERIFY opt-out) - line.py: webhook events capped at LINE_WEBHOOK_EVENTS_MAX (100) - discord.py: format_message blank-line collapse + code block preservation - discord.py: DISCORD_IGNORED_CONTENT support (embed text fields) - webhook.py: __received_at__ timestamp template tokens - adapter.py: _state_lock expanded to protect _pending_buttons/_media_tokens - adapter.py: _WEBHOOK_EVENTS_MAX constant shared via gateway/config.py - adapter.py: redundant import os as _os removed (use top-level import) - test_line_plugin.py: env leak fix for LINE_PUBLIC_URL/LINE_HOME_CHANNEL - discord.py/slack.py: _mark_connected()/_mark_disconnected() lifecycle hooks Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…, dotenv fallback - docker-compose.yml: add GLM_API_KEY, GLM_BASE_URL, OPENROUTER_API_KEY env vars - docker-compose.yml: add LINE_SKIP_SIGNATURE_VERIFY with false default - docker-compose.yml: fix dashboard image name (hermes-dashboard → hermes-agent) - .env.example: clarify GLM_BASE_URL coding plan vs standard plan endpoints - gateway/run.py, hermes_cli/env_loader.py: try/except ModuleNotFoundError for dotenv - cron/scheduler.py: add standalone setup_logging(mode="cron") guard - cron/scheduler.py: demote "No jobs due" log from INFO to DEBUG Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- AGENTS.md: add Dev Record Conventions section (one-layer flat tracking) - gateway/config.py: add shared LINE_WEBHOOK_EVENTS_MAX = 100 constant Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Replace direct _running=True/False with _mark_connected()/_mark_disconnected() to align with Telegram/LINE lifecycle pattern and enable runtime status reporting. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- discord.py: change format_message line.strip() to rstrip() to preserve Markdown indentation (nested lists, blockquotes, indented code) - run.py: integrate agent.error_classifier into gateway error handling to log structured FailoverReason (retryable/should_fallback) - test_discord_format_and_ignore.py: update test_trim_each_line assertion for rstrip-only behavior Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- base.py: add @functools.lru_cache(maxsize=1) to _detect_macos_system_proxy
to amortize scutil(3s) from per-call to once-per-process
- test_google_chat.py: use Platform("google_chat") dynamic member instead
of Platform.GOOGLE_CHAT static enum (plugin uses dynamic, not registered)
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…rm("google_chat")
All 6 static references updated to dynamic member resolution.
Google Chat is a plugin-only platform, not registered in core Platform enum.
157/157 pass, full tests/gateway/ 56→51 failed (-5, no new failures).
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…afety
- test_google_chat.py: replace all remaining Platform.GOOGLE_CHAT static
refs with Platform("google_chat") dynamic member (plugin-only platform)
- test_send_image_file.py: replace patch.object(discord, "File") with
direct sys.modules mutation + try/finally restore to prevent xdist
cross-test state pollution
- tests/gateway/ full: 56→50 failed (-6, no new failures)
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
YoshitsuguOtani
marked this pull request as ready for review
August 4, 2026 07:45
This was referenced Aug 5, 2026
Contributor
|
too large to review safely This PR changes 2326 production lines before tests and docs. Please split it or add a focused justification if it should stay together. Signed: GPT-5.6-luna-high in Codex |
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.
Summary
LINE + Discord 品質改善、セキュリティ強化、state_lock保護、テスト修正、Dev Record Conventions の包括的変更。
コミット(6件)
変更ファイル(16 files, +551/-52 lines)
テスト