Split ChannelRuntime megamodule into 8 packages (#263)#451
Split ChannelRuntime megamodule into 8 packages (#263)#451
Conversation
Physically delete agents/Aevatar.GAgents.ChannelRuntime/ and redistribute
its 92 .cs files + proto messages across 8 packages along clean
architectural lines:
- Aevatar.GAgents.Channel.Runtime: ChannelBotRegistration projection
pipeline, conversation pipeline middleware, tombstone compaction with
pluggable ITombstoneCompactionTarget, channel-agnostic streaming sink
and inbound-message types.
- agents/channels/Aevatar.GAgents.Channel.NyxIdRelay: NyxId-relay-specific
files (provisioning services, callback endpoints, scope resolver,
ownership verifier, scope backfill, platform reply service, IPlatformAdapter,
outbound dispatcher).
- Aevatar.GAgents.NyxidChat: Nyx-specific runtime impls
(ChannelLlmReplyInboxRuntime, ChannelConversationTurnRunner,
NyxIdConversationReplyGenerator) -- avoids the NyxidChat <-> NyxIdRelay
circular that would form if these landed in the relay channel package.
- agents/platforms/Aevatar.GAgents.Platform.Lark: Lark-specific helpers
(LarkConversationInboxRuntime, conversation targets, error codes, host
defaults, proxy response).
- Aevatar.GAgents.Authoring (new): card-driven agent builder flow
(AgentBuilderCardFlow, NyxRelayAgentBuilderFlow, AgentBuilderTool,
FeishuCardHumanInteractionPort) -- Lark-specific per RFC §9.4.
- Aevatar.GAgents.Scheduled (new): SkillRunnerGAgent, WorkflowAgentGAgent,
UserAgentCatalog state/projector/query, channel schedule + workflow text
routing.
- Aevatar.GAgents.Device (new): Device registration projection pipeline
+ DeviceEventEndpoints.
- src/Aevatar.AI.ToolProviders.{ChannelAdmin,AgentCatalog} (new):
IAgentTool surfaces (ChannelRegistrationTool, AgentDeliveryTargetTool)
pulled out of the runtime so Channel.Runtime / Scheduled stay AI-clean.
ServiceCollectionExtensions split along the same lines; Mainnet host
composes per-package AddXxx() calls. Single channel_runtime_messages.proto
split into channel_bot_registration / device_registration /
user_agent_catalog / skill_runner / workflow_agent under their owning
packages. Removed three forward-looking stub messages from
conversation_events.proto that were #258 scaffolding superseded by the
legacy production schemas now in channel_bot_registration.proto.
Build slice: aevatar.agents.slnf created for new agent packages, added
to solution_split_guards. ToolProviders additions go in aevatar.ai.slnf.
CI guards (channel_card_literal_guard, channel_relay_nyx_chat_direct_create_guard,
channel_tombstone_proto_field_guard) updated to new paths.
Tests: ChannelRuntime.Tests stays as the integration test surface and
references all 8 new packages; 465/465 pass. The two pre-existing Mainnet
hosting test failures (BindAsync on IStudioMemberService) reproduce on
origin/dev unchanged.
Closes #263.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 674efdd6e6
ℹ️ 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".
| builder.Services.AddScheduledAgents(builder.Configuration); | ||
| builder.Services.AddAgentAuthoring(); | ||
| builder.Services.AddNyxIdRelayChannel(); | ||
| builder.Services.AddLarkPlatform(); |
There was a problem hiding this comment.
Register Telegram platform services in mainnet composition
After splitting the monolithic AddChannelRuntime, the mainnet host now wires AddLarkPlatform() but never wires AddTelegramPlatform(). NyxIdRelayOutboundPort resolves IMessageComposer/IChannelNativeMessageProducer by platform, so Telegram outbound paths now hit runtime composer_not_found/missing producer behavior for Telegram registrations even though Telegram tools are enabled. Add Telegram platform DI registration in this composition path to preserve the pre-split runtime behavior.
Useful? React with 👍 / 👎.
| @@ -0,0 +1,116 @@ | |||
| syntax = "proto3"; | |||
|
|
|||
| package aevatar.gagents.channel.runtime; | |||
There was a problem hiding this comment.
Keep legacy Any type URLs for channel bot contracts
This package rename changes protobuf full names from the legacy aevatar.gagents.channelruntime.* namespace to aevatar.gagents.channel.runtime.*, which changes Any.TypeUrl values for channel-bot payloads. Existing persisted events/snapshots with old type URLs will no longer unpack unless explicit legacy aliases are provided (the repo already uses this pattern for UserAgentCatalog), so replay/state restoration for pre-split data can silently stop matching handlers after upgrade.
Useful? React with 👍 / 👎.
Architecture review: file split (#451)Overall the split is well-executed — clean boundaries, correct DI inversion, and proper plugin pattern for tombstone compaction. Below are specific issues found, ordered by severity. ❌ Critical / bug
If Telegram has any production traffic, Even if Telegram is not currently active on mainnet, the asymmetry between Lark (called) and Telegram (not called) is a regression risk — a future activation would silently fail without CI catching it. Add
|
- agents/Aevatar.GAgents.ChannelRuntime/ChannelUserConfigScope.cs (added on dev for issue #436 / PR #438) → moved to agents/Aevatar.GAgents.Channel.Runtime/ with namespace updated to Aevatar.GAgents.Channel.Runtime, visibility lifted to public so Authoring's AgentBuilderTool / AgentBuilderCardFlow can compose per-Lark-user scopes across the package boundary. - src/platform/Aevatar.GAgentService.Governance.Hosting/Endpoints/ ServiceBindingEndpoints.cs: take origin/dev (PR #433 / 9e1737e / 6860b02 refined the validation/handler-order shape). - test/Aevatar.GAgentService.Integration.Tests/GovernanceEndpointsTests.cs: take origin/dev (matches the refined endpoint shape). ChannelUserConfigScopeTests: add `using Aevatar.GAgents.Channel.Runtime;` for the relocated type. 473/473 ChannelRuntime tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eanzhao
left a comment
There was a problem hiding this comment.
架构审查补充(基于最新 head 17262f8e):拆分方向可以,但下面这些边界仍不干净。
| { | ||
| ArgumentNullException.ThrowIfNull(services); | ||
|
|
||
| services.Replace(ServiceDescriptor.Singleton<IHumanInteractionPort, FeishuCardHumanInteractionPort>()); |
There was a problem hiding this comment.
这里不能用通用 AddAgentAuthoring() 去全局替换 IHumanInteractionPort 为 Feishu/Lark 实现。Aevatar.GAgents.Authoring 的包名和入口现在看起来是 channel-neutral,但实现与依赖已经是 Lark-specific(FeishuCardHumanInteractionPort、Platform.Lark)。RFC §9.4 也要求要么命名为 Lark-specific,要么拆 Authoring.Abstractions + Authoring.Lark。按现在这样,未来任何非 Lark host 只要组合 Authoring 就会拿到 Feishu port,边界不诚实。建议改成 Aevatar.GAgents.Authoring.Lark/AddLarkAgentAuthoring(),或抽窄通用 authoring contract 后由 Lark 包注册实现。
| @@ -0,0 +1,72 @@ | |||
| syntax = "proto3"; | |||
|
|
|||
| package aevatar.gagents.device; | |||
There was a problem hiding this comment.
这个 package rename 会改变所有 DeviceRegistration Any.TypeUrl/CLR full name(legacy aevatar.gagents.channelruntime.* -> aevatar.gagents.device.*)。仓库里已有持久 actor state/domain events/readmodel payload,升级后没有 LegacyProtoFullName/LegacyClrTypeName alias 就不能从旧事件/快照恢复。UserAgentCatalog 已做 alias,但 Device、SkillRunner、WorkflowAgent 这类拆出来的持久契约没有成套覆盖;ChannelBot 也有同类问题。这里不是单纯文件拆分,必须补兼容 alias + replay/unpack 测试,或保持 proto package/full name 不变。
| return; | ||
|
|
||
| var actor = await _actorRuntime.GetAsync(actorId); | ||
| var actor = await _actorRuntime.GetAsync(target.ActorId); |
There was a problem hiding this comment.
ChannelRuntimeTombstoneCompactor 不应该解析 actor 实例后直接调用 HandleEventAsync。这次拆分把它变成跨包 compaction plugin,继续绕过 IActorDispatchPort 会把 runtime lookup 和 message delivery 重新揉在同一个服务里,也绕开了标准 inbox/dispatch 契约。ITombstoneCompactionTarget 可以暴露 actor id + command payload,但 compactor 应该通过 runtime 只做必要 lifecycle,再用 IActorDispatchPort.DispatchAsync 投递 envelope(类似 ChannelBotRegistrationStoreCommands 的方向)。
| using Aevatar.AI.Abstractions.ToolProviders; | ||
| using Aevatar.AI.ToolProviders.NyxId; | ||
| using Aevatar.Foundation.Abstractions; | ||
| using Aevatar.GAgents.Channel.Runtime; |
There was a problem hiding this comment.
拆到 ToolProviders 后,这个类仍然不是薄 adapter:它会启动 projection、读 state version、创建 catalog actor、手写 envelope,并直接 HandleEventAsync。这违反命令骨架/dispatch 边界,也把 projection priming 放进 tool execution 路径。建议把 upsert/delete 收敛到窄的 UserAgentCatalogCommandPort/application service,由该端口用 IActorDispatchPort 返回诚实的 accepted/observed 状态;AI tool 只负责参数映射和结果格式化。
Architecture Review: File Split QualityOverall the split is well-executed — clean DAG, no circular deps, namespaces consistent with directories, proto namespaces match packages. A few issues worth addressing: 1.
|
…port boundary Resolves the six inline review comments on PR #451 (Codex P1 + 4 from @eanzhao + the proto-compat regression they share). ### 1. Re-add AddTelegramPlatform() in Mainnet host (Codex P1) Original split moved the Telegram inline registration out of the legacy SCE but the merge with origin/dev dropped the host-side wire-up. Add AddTelegramPlatform() to MainnetHostBuilderExtensions so NyxIdRelayOutboundPort can resolve IMessageComposer / IChannelNativeMessageProducer for Telegram-platform replies. ### 2. Legacy proto / CLR aliases for renamed packages (Codex P1 + @eanzhao MED) Splitting the proto package + csharp_namespace would break Any.TypeUrl unpack and CLR full-name resolution against pre-split persisted events / snapshots. Add [LegacyProtoFullName] / [LegacyClrTypeName] partial-class aliases (mirroring UserAgentCatalogLegacyAliases from #260) for every persisted type rolled into a new namespace: - ChannelBotRegistrationLegacyAliases.cs (12 messages incl. ChannelInboundEvent) - DeviceRegistrationLegacyAliases.cs (9 messages) - SkillRunnerLegacyAliases.cs (12 messages) - WorkflowAgentLegacyAliases.cs (11 messages) - UserAgentCatalogLegacyAliases.cs extended with UserAgentCatalogNyxCredentialDocument ### 3. Rename Authoring → Authoring.Lark (@eanzhao HIGH) Package and SCE method were channel-neutral on the surface but the implementation (FeishuCardHumanInteractionPort, AgentBuilderCardFlow's hard-coded Lark p2p / card_action semantics, Platform.Lark dependency) is Lark-specific. Renamed: - agents/Aevatar.GAgents.Authoring → agents/Aevatar.GAgents.Authoring.Lark - Csproj + namespace + slnx + slnf paths - AddAgentAuthoring() → AddLarkAgentAuthoring() - channel_card_literal_guard scan_project entry Reflects RFC §9.4 option (a): "Authoring is currently Lark-only — name it that way; defer Authoring.Abstractions split until a second channel needs authoring." ### 4. Tombstone compactor: dispatch via IActorDispatchPort (@eanzhao HIGH) ChannelRuntimeTombstoneCompactor previously called actor.HandleEventAsync directly after IActorRuntime.GetAsync, mixing runtime lookup and delivery and bypassing the standard inbox/dispatch contract. Refactored: - ITombstoneCompactionTarget gains EnsureActorAsync(IActorRuntime, ct) so each plugin owns its own GAgent type lifecycle. - ChannelRuntimeTombstoneCompactor takes IActorDispatchPort and uses EnvelopeRouteSemantics.CreateDirect(publisherId, target.ActorId) followed by dispatchPort.DispatchAsync(...). Runtime is only used through the target's EnsureActorAsync. - All three target impls (ChannelBotRegistration, Device, UserAgentCatalog) implement EnsureActorAsync. - Existing tombstone compactor tests updated to assert on IActorDispatchPort.DispatchAsync instead of IActor.HandleEventAsync. ### 5. AgentDeliveryTargetTool: thin adapter via IUserAgentCatalogCommandPort (@eanzhao HIGH) Tool was orchestrating projection priming, state-version polling, actor lifecycle, and direct envelope dispatch — way past the LLM-tool mandate of "param mapping + result formatting". Extracted: - IUserAgentCatalogCommandPort with UpsertAsync / TombstoneAsync returning honest CatalogCommandOutcome (Accepted / Observed / NotFound). - UserAgentCatalogCommandPort implementation owns projection priming, envelope construction, IActorDispatchPort.DispatchAsync, and the state-version polling loop. - AgentDeliveryTargetTool now: validate args, resolve current owner via NyxID, call commandPort, format result. ~70 lines of wait/dispatch logic gone from the tool. - Tests updated to mock IUserAgentCatalogCommandPort. 473/473 ChannelRuntime.Tests pass; full slnx retains only the same two pre-existing Mainnet hosting BindAsync failures that reproduce on origin/dev. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
回复审查意见 + 6 条 inline review(commit 对你的 3 条
对 inline review 的 6 条
测试:473/473 ChannelRuntime.Tests 通过;全 slnx 仅剩 dev 上预先存在的 2 个 Mainnet hosting |
Origin/dev gained Studio catalog ETag + Daily pipeline doc commits since the last merge. The only structural conflict was the directory-rename split warning git emitted because origin/dev still tries to add `ChannelUserConfigScope.cs` to the deleted `agents/Aevatar.GAgents.ChannelRuntime/` path; the file already lives at `agents/Aevatar.GAgents.Channel.Runtime/` with the namespace bumped to `Aevatar.GAgents.Channel.Runtime` and visibility lifted to `public` (Authoring.Lark needs cross-package access). Dropped the duplicate in the legacy path and kept the authoritative copy at the new location. ChannelUserConfigScopeTests gets the same `using Aevatar.GAgents.Channel.Runtime;` import retained from the prior merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## dev #451 +/- ##
==========================================
+ Coverage 70.90% 70.96% +0.05%
==========================================
Files 1209 1216 +7
Lines 87045 87762 +717
Branches 11411 11508 +97
==========================================
+ Hits 61722 62282 +560
- Misses 20876 20978 +102
- Partials 4447 4502 +55
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…elper Resolves three followup architecture-review points on PR #451: ### Channel.Runtime drops AI / Workflow direct deps (review #2) `Channel.Runtime`'s csproj used to pull in `Aevatar.AI.Abstractions` and `Aevatar.Workflow.Application.Abstractions` because of two files that straddled the channel/AI and channel/workflow boundary: - `ChannelContextMiddleware` (an `ILLMCallMiddleware` impl) — moved to `Aevatar.GAgents.NyxidChat`, which is the only package that needs it and already references `AI.Abstractions`. NyxidChat SCE registers it for the LLM call pipeline; Channel.Runtime SCE no longer touches `ILLMCallMiddleware`. - `ChannelCardActionRouting` (builds `WorkflowResumeCommand`) — moved to `Aevatar.GAgents.NyxidChat` for the same reason. Its sole consumer (`ChannelConversationTurnRunner`) lives there too. `Channel.Runtime.csproj` now references only `Channel.Abstractions`, `Foundation.Abstractions`/`Core`, and the `CQRS.Projection.*` slice — matching the "channel-agnostic flow + projection infrastructure" charter from the RFC. Tests (`ChannelCardActionRoutingTests`) get the extra `using Aevatar.GAgents.NyxidChat;`. ### Extract Elasticsearch projection-store toggle helper (review #4) The `ResolveElasticsearchEnabled` + `BuildElasticsearchOptions` helper pair was duplicated three times (Channel.Runtime / Device / Scheduled SCEs) with slightly different log strings and `Console.Error.WriteLine` output. Centralized into `Aevatar.CQRS.Projection.Providers.Elasticsearch.DependencyInjection.ElasticsearchProjectionConfiguration` with two static helpers: - `IsEnabled(IConfiguration?, ILogger?, string? storeName)` — explicit flag → endpoints presence → false; logs a structured warning via `ILogger` (when supplied) instead of `Console.Error.WriteLine`. - `BindOptions(IConfiguration)` — typed binder for `ElasticsearchProjectionDocumentStoreOptions`. All three SCEs now call into this helper; per-package warning text is parameterized via `storeName`. Section path (`Projection:Document:Providers:Elasticsearch`) is exposed as a const so future call sites stay in sync. ### Followup points acknowledged but deferred - **Cross-package dep chain `NyxidChat → Authoring.Lark → Scheduled → Platform.Lark`** (review #1) — pre-existing arch debt that the split surfaced rather than introduced. Cleaner would be to invert via `IInboundFlowResolver` plug-ins so `ChannelConversationTurnRunner` doesn't reach into `AgentBuilderCardFlow` directly. Out of scope for the package split; tracking as a separate follow-up. - **Tombstone compactor "central coordinator"** (review #3) — `Channel.Runtime` defines `ITombstoneCompactionTarget` but does not reference `Device` / `Scheduled` at the csproj level; per-package targets register themselves through DI. The plug-in pattern is intentional and keeps the DAG one-way. - **`Scheduled` package name vs UserAgentCatalog content** (review #5) — `UserAgentCatalog` is the delivery-target registry that Scheduled agents read at execution time to route output, so co-locating it with `SkillRunnerGAgent` / `WorkflowAgentGAgent` is intentional. Renaming to `AgentCatalog` would split actors from their primary consumer; deferring. 473/473 ChannelRuntime.Tests pass; full slnx still only fails the same two pre-existing Mainnet hosting `BindAsync on IStudioMemberService` tests that reproduce on origin/dev. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
对补充审查的回应(commit `88a0f100`): 采纳并修复
保留并说明
验证:473/473 ChannelRuntime.Tests 通过;全 slnx 只剩 dev pre-existing 的 2 个 Mainnet hosting BindAsync 失败。 |
Fix review: all issues resolved ✅已验证修复
确认无退化
已确认推迟
所有审查项均已处理。该 PR 已经可以合并。 |
Follow-up Review: Fix Commits (d1249f9 → 88a0f10)Fixed ✅ES helper deduplication — Channel.Runtime layer tightening — Still outstandingNyxidChat.csproj duplicate Everything else from the previous review is either properly fixed or acknowledged with valid rationale (tombstone plugin design, Scheduled naming, pre-existing Studio deps). Ship-ready given the remaining item is trivial. |
eanzhao
left a comment
There was a problem hiding this comment.
Reviewed latest fix commit 88a0f100. Most previous split issues are fixed; one responsibility-boundary issue remains in the Lark authoring package.
| using Google.Protobuf.WellKnownTypes; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
There was a problem hiding this comment.
这里把包名改成 Authoring.Lark 是对的,但这个 adapter 里仍然保留了核心编排:AgentBuilderTool 直接从 DI 拿 IActorRuntime,随后在 create/run/disable/enable/delete 路径里直接 CreateAsync + HandleEventAsync 到 SkillRunnerGAgent / WorkflowAgentGAgent / UserAgentCatalogGAgent,并且自己做 EnsureUserAgentCatalogProjectionAsync + polling。按当前分层,LLM/tool 层应该只做参数映射和 Nyx/Lark adapter 交互;scheduled agent lifecycle、catalog tombstone、projection priming/观察这些应收敛到 Scheduled application command port(类似这次新增的 IUserAgentCatalogCommandPort,或一个专门的 agent lifecycle command port)并通过 IActorDispatchPort 投递。否则文件虽然拆了,核心业务编排仍留在 Lark authoring adapter 里,边界没有真正清掉。
Per @eanzhao's followup review: NyxidChat.csproj had two identical `Channel.Abstractions` ProjectReference lines. Both pointed at the same csproj, so the duplication was a no-op for build but stale in the repo. Keep the first (alongside Channel.Runtime / NyxIdRelay / Authoring.Lark / Scheduled), drop the second. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves @eanzhao's open responsibility-boundary concern on PR #451. `AgentBuilderTool` (Authoring.Lark) was still pulling `IActorRuntime` out of DI and orchestrating SkillRunner / WorkflowAgent / UserAgentCatalog lifecycle directly: `actor.Created/HandleEventAsync` for create / run / disable / enable, plus inline projection priming (`EnsureUserAgentCatalogProjectionAsync`) and envelope construction. That kept the LLM tool layer in the command-skeleton path even after the package split. ### New scheduled-agent command ports (`Aevatar.GAgents.Scheduled`) - `ISkillRunnerCommandPort` + `SkillRunnerCommandPort` — owns `Initialize` (with optional first-run trigger) / `Trigger` / `Disable` / `Enable` for SkillRunner. Resolves the actor lifecycle via `IActorRuntime`, primes UserAgentCatalog projection, and dispatches via `IActorDispatchPort.DispatchAsync` with `EnvelopeRouteSemantics.CreateDirect(publisherId, agentId)`. - `IWorkflowAgentCommandPort` + `WorkflowAgentCommandPort` — same shape for WorkflowAgent. `TriggerAsync` carries optional `revisionFeedback` for re-runs. - DI registers both alongside the existing `IUserAgentCatalogCommandPort` in `ScheduledServiceCollectionExtensions`. ### `AgentBuilderTool` now thin adapter - `ExecuteAsync` resolves the three command ports + `nyxClient` + read-side `queryPort` (no more `IActorRuntime`). DI guard rejects if any port is missing. - `CreateDailyReportAgentAsync` / `CreateSocialMediaAgentAsync` call `skillRunnerPort.InitializeAsync` / `workflowAgentPort.InitializeAsync` with `runImmediately`. Workflow path follows the existing "wait for catalog confirmation, then fire trigger" pattern via `workflowAgentPort.TriggerAsync(..., revisionFeedback: null, ...)`. - `RunAgentAsync` / `DisableAgentAsync` / `EnableAgentAsync` call the unified `TryDispatchLifecycleAsync` helper which dispatches through the typed port matching the catalog entry's `AgentType`. - `DeleteAgentAsync` calls `TryDispatchLifecycleAsync` for the pre-tombstone disable, then `catalogCommandPort.TombstoneAsync` (which returns honest `Observed`/`Accepted`/`NotFound` outcome). - Dead helpers `BuildDirectEnvelope` and `EnsureUserAgentCatalogProjectionAsync` removed — both behaviors now belong to the command-port impls. ### Tests - `AgentBuilderToolTests` (30 tests in `test/Aevatar.GAgents.ChannelRuntime.Tests/`) migrated to mock the three command ports instead of `IActorRuntime` / `IActor`. Assertions verify port calls (`Received(1).InitializeAsync(...)`, `TriggerAsync(...)` etc.) instead of envelope-shape predicates on `actor.Received().HandleEventAsync(...)`. - All 473 ChannelRuntime.Tests pass; full slnx still only fails the pre-existing dev `BindAsync on IStudioMemberService` Mainnet hosting tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
对最新 review 的回应(commit `d5be9346`): 采纳并修复:
仍待 codecov/patch 检查。如果 patch coverage 仍欠,下一轮会补 `SkillRunnerCommandPort` / `WorkflowAgentCommandPort` / `UserAgentCatalogCommandPort` 的直接单元测试 + `ElasticsearchProjectionConfiguration` 测试。 |
Boost codecov/patch coverage on PR #451 above the 70.90% gate by adding focused unit tests for the new code introduced in earlier review-fix commits. ChannelRuntime.Tests grows from 467 → 547 (+80 with theory expansion); 547/547 green. ### Tests added (5 new files in test/Aevatar.GAgents.ChannelRuntime.Tests/) - `SkillRunnerCommandPortTests.cs` — 20 cases. Initialize with / without `runImmediately`, Trigger/Disable/Enable command + reason routing, null/whitespace agentId guards, null-command guard, null- dependency ctor guards, envelope route assertions (PublisherActorId=="scheduled.skill-runner", Direct.TargetActorId == agentId). - `WorkflowAgentCommandPortTests.cs` — 20 cases. Mirror coverage; also asserts the extra `revisionFeedback` parameter lands on `TriggerWorkflowAgentExecutionCommand.RevisionFeedback`. - `UserAgentCatalogCommandPortTests.cs` — 14 cases. Uses the internal shrunk-budget ctor (3 attempts × 1 ms). Covers Upsert→Observed (state-version advance + entry match), Upsert→Accepted (budget exhausts), Tombstone→NotFound (no entry), Tombstone→Observed (entry vanishes / version null), Tombstone→Accepted, actor lifecycle ensure, argument guards. - `ElasticsearchProjectionConfigurationTests.cs` — 11 cases. `IsEnabled` paths: null configuration, explicit true/false, case-insensitive, endpoints auto-detect, warning logged when section present but no flag/endpoints, no log when null logger, no log when endpoints populated. `BindOptions`: null throws, full bind, empty section → defaults. - `TombstoneCompactionTargetTests.cs` — 9 cases. Smoke tests for all three `*TombstoneCompactionTarget` impls (ChannelBotRegistration, Device, UserAgentCatalog) — Get-or-Create lifecycle, CreateCommand payload version, property smoke. ### No production code changes Tests rely on existing `InternalsVisibleTo` for ChannelRuntime.Tests plus the package's existing `internal` surface (e.g. the test-only `UserAgentCatalogCommandPort(... shrunk wait)` ctor). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Physically delete
agents/Aevatar.GAgents.ChannelRuntime/and redistribute its 92.cs+ 1.protoacross 8 packages along clean architectural lines. Closes #263.Destination map
agents/Aevatar.GAgents.Channel.Runtime(extended)ITombstoneCompactionTarget) + generic streaming sink +InboundMessageagents/channels/Aevatar.GAgents.Channel.NyxIdRelay(extended)IPlatformAdapter+ scope backfill + outbound dispatcheragents/Aevatar.GAgents.NyxidChat(extended)ChannelLlmReplyInboxRuntime/ChannelConversationTurnRunner/NyxIdConversationReplyGenerator— landed here (not in the relay channel package) to avoid the NyxidChat ↔ NyxIdRelay dep cycleagents/platforms/Aevatar.GAgents.Platform.Lark(extended)agents/Aevatar.GAgents.Authoring(new)FeishuCardHumanInteractionPort+NyxRelayAgentBuilderFlow(Lark-specific single package per RFC §9.4 option a)agents/Aevatar.GAgents.Scheduled(new)SkillRunnerGAgent/WorkflowAgentGAgent/UserAgentCatalog*(#260) /ChannelScheduleCalculator/Runner/ChannelWorkflowTextRoutingagents/Aevatar.GAgents.Device(new)DeviceEventEndpointssrc/Aevatar.AI.ToolProviders.ChannelAdmin(new)ChannelRegistrationTool/ Source — operates onChannelBotRegistration*src/Aevatar.AI.ToolProviders.AgentCatalog(new)AgentDeliveryTargetTool/ Source — operates onUserAgentCatalog*Architectural decisions
ChannelRegistrationToolandAgentDeliveryTargetTooloperate on channel bot registration / user agent catalog state respectively. They land insrc/Aevatar.AI.ToolProviders.*(matching repo convention) soChannel.Runtime/Scheduledonly take a dependency onAevatar.AI.Abstractions(middleware interface contracts), not onAI.Coreor anyAI.ToolProviders.*implementation.Aevatar.GAgents.Authoring.Lark, registration method isAddLarkAgentAuthoring(). Reflects the Lark-specific reality (FeishuCardHumanInteractionPort, AgentBuilderCardFlow hard-coded p2p / card_action semantics, Platform.Lark dep); deferring anAuthoring.Abstractionssplit until a second channel needs authoring.ChannelRuntimeTombstoneCompactoriteratesIEnumerable<ITombstoneCompactionTarget>; each owning package (Channel.Runtime, Device, Scheduled) registers its own target.conversation_events.protothat were [Channel RFC] Runtime package (grains / middleware / durable inbox integration) #258 architectural scaffolding (transport_binding-shapedChannelBotRegistrationEntry/UserAgentCatalogEntry/DeviceRegistrationEntry) that conflicted name-for-name with the legacy production schemas now inchannel_bot_registration.protoetc. Stubs had no production callers.Build slice
aevatar.agents.slnfforAuthoring/Scheduled/Device(registered insolution_split_guards.sh).ToolProviders.ChannelAdmin/ToolProviders.AgentCatalogjoinaevatar.ai.slnf.aevatar.channels.slnfandaevatar.platforms.slnfalready cover Channel.Runtime / channels/* / platforms/*.DI
Old 280-line monolithic
AddChannelRuntimesplit into per-package SCEs:AddChannelRuntime(IConfiguration?)(extended)AddDeviceRegistration(IConfiguration?),AddScheduledAgents(IConfiguration?)(new)AddAgentAuthoring()(new)AddNyxIdRelayChannel()(new)AddLarkPlatform()(new),AddTelegramPlatform()(new — Telegram registration was inline in the old SCE; gave it a proper DI surface)AddChannelAdminTools(),AddAgentCatalogTools()(new)Mainnet host wiring composes them in order.
Test plan
dotnet build aevatar.slnx— 0 errorsdotnet test test/Aevatar.GAgents.ChannelRuntime.Tests— 465/465 passdotnet test aevatar.slnx— only 2 pre-existing failures (MainnetHealthEndpointsTests/MainnetHostCompositionTestsBindAsync method found on IStudioMemberService with incorrect format— verified to also fail onorigin/devunchanged; not introduced by this PR)bash tools/ci/architecture_guards.sh— all guards pass;playground_asset_drift_guardskipped (local pnpm/tsc env)bash tools/ci/solution_split_guards.sh— all 9 slnf slices build cleanDeferred follow-ups
test/Aevatar.GAgents.ChannelRuntime.Tests/'s 53 test files currently stay together (referenced by all 8 new packages, runs as integration surface). Issue acceptance asks to split them to per-package*.Tests/— non-blocking for the architectural split.IPlatformAdaptercleanup — the legacy interface still has runtime field references inChannelConversationTurnRunner(no production impl, only test stubs). Pure dead-code removal, separate PR.References
🤖 Generated with Claude Code