Skip to content

feat: channel runtime with Telegram + Lark adapters#139

Merged
eanzhao merged 30 commits intodevfrom
feature/bot
Apr 10, 2026
Merged

feat: channel runtime with Telegram + Lark adapters#139
eanzhao merged 30 commits intodevfrom
feature/bot

Conversation

@eanzhao
Copy link
Copy Markdown
Contributor

@eanzhao eanzhao commented Apr 7, 2026

Summary

Closes #113

Implements the channel runtime architecture where Aevatar owns bot platform webhooks directly, using NyxID as the credentialed bot API provider for outbound messaging.

What's included

ChannelRuntime framework (agents/Aevatar.GAgents.ChannelRuntime/)

  • IPlatformAdapter abstraction for multi-platform support
  • ChannelBotRegistrationStore: persistent protobuf-based config
  • ChannelCallbackEndpoints: webhook receiver + registration CRUD
  • Async acknowledge-then-respond pattern (platform webhook timeouts)

Lark adapter — URL verification challenge, im.message.receive_v1 parsing, outbound reply via Nyx api-lark-bot proxy

Telegram adapter (NEW) — Parse Telegram Update webhooks, reply via Nyx api-telegram-bot proxy, ignore bot/non-text messages

setWebhook auto-config (NEW) — On registration with webhook_base_url, automatically call Telegram setWebhook via NyxID proxy so Telegram sends updates directly to Aevatar

Security — Token verification for Lark callbacks, registration-scoped actor keys, session_id-correlated stream event filtering

MockNyxId — Local dev server with mock auth, proxy, LLM, and setWebhook endpoints

Architecture

Telegram/Lark → POST /api/channels/{platform}/callback/{registrationId} → Aevatar
Aevatar → NyxIdApiClient.ProxyRequestAsync("api-telegram-bot"/"api-lark-bot", ...) → Nyx → Platform

New registration flow (Telegram)

POST /api/channels/registrations
{
  "platform": "telegram",
  "nyx_provider_slug": "api-telegram-bot",
  "nyx_user_token": "...",
  "webhook_base_url": "https://aevatar-host"
}
# → Auto-calls Telegram setWebhook via NyxID proxy
# → Returns callback_url for Telegram to send updates to

Test plan

  • dotnet build aevatar.slnx — passes
  • 27 unit tests pass (Lark adapter: 19, Telegram adapter: 8)
  • Integration: register Telegram bot, verify setWebhook + end-to-end message flow

🤖 Generated with Claude Code

eanzhao and others added 7 commits April 6, 2026 17:37
Implement the channel runtime architecture (issue #113) that allows
Aevatar to receive bot platform webhooks directly instead of relying
on NyxID as a relay middleman. Nyx remains the credentialed outbound
provider for sending messages.

New project: agents/Aevatar.GAgents.ChannelRuntime with:
- IPlatformAdapter abstraction for multi-platform support
- LarkPlatformAdapter: URL verification + im.message.receive_v1 parsing
- ChannelBotRegistrationStore: persistent protobuf-based config store
- ChannelCallbackEndpoints: webhook receiver + registration CRUD
- Async acknowledge-then-respond pattern (platforms have short timeouts)
- Outbound replies via NyxIdApiClient.ProxyRequestAsync

Existing NyxID relay path (/api/webhooks/nyxid-relay) is untouched.

Closes #113

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…apter)

15 tests covering:
- ChannelBotRegistrationStore: CRUD operations, persistence, edge cases
- LarkPlatformAdapter: URL verification, message parsing, bot filtering,
  empty text handling, missing header handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensure that session completion response is published immediately to consumers (relay, SSE) before attempting persistence. Persistence is now best-effort with logging for failures, preventing concurrency conflicts from blocking the reply to the user.
Standalone ASP.NET minimal API server at tools/Aevatar.Tools.MockNyxId
that simulates the NyxID external service. Enables end-to-end testing
of channel runtime, LLM calls, and proxy operations without a real
NyxID instance.

Endpoints:
- GET  /api/v1/users/me — mock user info
- POST /api/v1/auth/test-token — issue test JWTs
- GET  /api/v1/proxy/services — service discovery (api-lark-bot, api-github)
- *    /api/v1/proxy/s/{slug}/{**path} — catch-all proxy with Lark/Telegram-aware responses
- POST /api/v1/llm/gateway/v1/chat/completions — OpenAI-compatible (streaming + non-streaming)

Usage:
  dotnet run --project tools/Aevatar.Tools.MockNyxId
  export Aevatar__NyxId__Authority=http://localhost:5199

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Verify Lark callback token before accepting verification challenges
   and event callbacks — rejects forged payloads when a verification
   token is configured on the registration.

2. Include registration ID in channel actor key to prevent cross-tenant
   state bleed when two registrations share the same platform chat ID.

3. Filter subscribed stream events by session_id so overlapping
   callbacks for the same actor cannot consume each other's responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the autonomous home AI agent as described in issue #97.
HouseholdEntity inherits AIGAgentBase with a Perceive-Reason-Act loop
driven by stream events (sensor, camera, chat, heartbeat) with safety
guardrails (kill switch, rate limiting, debounce) and dynamic context
injection into LLM system prompt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TelegramPlatformAdapter: parse Telegram Update webhooks, reply via
  NyxID proxy sendMessage, ignore bot/non-text messages
- Auto-configure Telegram webhook on registration: when webhook_base_url
  is provided, call setWebhook via NyxID proxy so Telegram sends updates
  directly to Aevatar's callback endpoint
- Proto: add webhook_url field to ChannelBotRegistrationEntry
- Registration endpoint: accept webhook_base_url, auto-call setWebhook
- MockNyxId: add setWebhook mock response
- 8 new unit tests for Telegram adapter (27 total)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4560bb7599

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

eanzhao and others added 19 commits April 7, 2026 17:31
Implements the Agent-as-Tool pattern: NyxIdChatGAgent's LLM can call
the "household" tool to dispatch home automation requests to a
HouseholdEntity actor.

- HouseholdEntityTool: IAgentTool that dispatches HouseholdChatEvent
  to HouseholdEntity via IActorRuntime, returns environment state and
  last action as JSON
- HouseholdEntityToolSource: IAgentToolSource for auto-discovery
- ServiceCollectionExtensions.AddHouseholdEntityTools() for DI
- 10 new tests (32 total)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The NyxID proxy automatically prepends "bot<TOKEN>/" to the path, so we should pass only the bare method name (e.g., "sendMessage", "setWebhook") instead of the full path "bot/sendMessage". This fixes Telegram webhook registration and message sending operations.
Introduce ChannelUserGAgent to manage user identity state and orchestrate the full inbound message flow. This actor tracks sender identity, resolves access tokens, dispatches to chat actors, and sends replies via platform adapters. Move complex callback logic from ChannelCallbackEndpoints to the actor, simplifying the endpoint and improving separation of concerns.
Add NyxIdOrgToken metadata key to allow passing organization tokens alongside user tokens. Enhance NyxIdProxyTool to merge service discovery from both tokens and automatically fallback to org token when user token lacks access to a service. This enables users to access organization-level services even when they haven't personally connected them.

Update ChannelUserGAgent to pass both user and org tokens to chat requests. Also add configuration loading for distributed hosting to support deployment-specific settings.
…ice access

Introduce dual-token credential routing to allow channel users to access personal NyxID services while falling back to org-level services. Add IServiceDiscoveryCache abstraction with in-memory implementation to avoid N+1 calls. Secure registration endpoints and validate platform adapters. Update documentation with ADR-0008.
- Add DeviceEventEndpoints with CRUD registration and callback handling
- Integrate device events into HouseholdEntity for temperature, motion, and speech detection
- Add DeviceRegistrationStore for persistent device registration management
- Include comprehensive unit tests for new functionality
…ndling

- Introduce projection system for device registrations with materialization context, runtime lease, and projector
- Add query port for reading device registration data from projection store
- Extend Household entity to handle device inbound events (temperature, motion, speech, etc.)
- Register device event endpoints and configuration in host API
- Remove in-memory DeviceRegistrationStore and its tests in favor of actor-backed projection
- Update proto definitions to include device registration messages and device inbound event
… projection

Introduce event-sourced ChannelBotRegistrationGAgent to replace file-based store, enabling cluster deployment. Add projection pipeline for query-side read model with IChannelBotRegistrationQueryPort. Update ChannelCallbackEndpoints to dispatch commands to actor and query projection. Include unit tests for both DeviceRegistrationGAgent and ChannelBotRegistrationGAgent.

Refactor DeviceEventEndpoints to use synchronous dispatch and clarify actor lifecycle. Mark internal helper methods for testing via InternalsVisibleTo.
Add comprehensive unit tests for two projection classes (DeviceRegistrationProjector and ChannelBotRegistrationProjector) and their corresponding query ports. The tests verify correct projection behavior for valid events, multiple registrations, unrelated events, empty registrations, and entries with blank IDs. Query port tests validate document retrieval, listing, and edge cases.
…atibility

Update ParseCallbackPayload to handle NyxID's actual CallbackPayload structure
which uses sender.platform_id and nested conversation object, while preserving
support for legacy sender.id format. Update tests to cover both formats.
The method name ListAsync was changed to QueryAllAsync across query ports and their consumers to better reflect its purpose of querying all entries. This improves naming consistency within the codebase and aligns with similar query patterns. Corresponding test method names were also updated.
GAgents agent plugins contain HTTP endpoints and platform adapters validated by dedicated integration tests. Their unit-test coverage would measure boilerplate (proto-generated code, DI wiring, async state machine closures) rather than business logic, which would drown the core branch coverage signal.
Handle cases where dotnet test returns non-zero due to instrumentation warnings (e.g., assemblies that cannot be instrumented) while tests actually passed. The script now captures the exit code and only fails if no coverage XML files are produced, indicating real test failures. This prevents false CI failures from benign warnings.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.67%. Comparing base (ea09ca2) to head (8d72330).
⚠️ Report is 31 commits behind head on dev.

@@            Coverage Diff             @@
##              dev     #139      +/-   ##
==========================================
+ Coverage   81.65%   81.67%   +0.02%     
==========================================
  Files         756      739      -17     
  Lines       48362    46986    -1376     
  Branches     6407     6233     -174     
==========================================
- Hits        39488    38377    -1111     
+ Misses       6107     5916     -191     
+ Partials     2767     2693      -74     
Flag Coverage Δ
ci 81.67% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/Aevatar.AI.Core/RoleGAgent.cs 80.73% <100.00%> (+0.23%) ⬆️

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

在 codecov 配置中排除 agents 目录,避免其测试覆盖率影响整体报告。
eanzhao added 2 commits April 10, 2026 15:44
修复配置加载顺序问题,在MainnetDistributedHostBuilderExtensions中重新添加环境变量源,确保环境变量能正确覆盖Distributed.json中的配置。

在RoleGAgentReplayContractTests中添加测试用例,验证当持久化RoleChatSessionCompletedEvent失败时,系统仍能正常发布TextMessageEndEvent响应,确保用户体验不受后台持久化故障影响。
添加 MainnetDistributedHostBuilderExtensionsTests 测试,验证 AEVATAR_ 前缀环境变量和无前缀环境变量能正确覆盖 Distributed.json 默认配置。新增 EnvironmentVariableScope 辅助类用于测试中安全设置环境变量。
@eanzhao eanzhao merged commit fc43d97 into dev Apr 10, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

integrate bot callbacks/channel runtime in Aevatar; use Nyx bot API providers for outbound messaging

1 participant