Parent RFC : #254
Implementation PR : #288
Wire-through follow-up : #294
Scope
从 LarkPlatformAdapter 迁移到 LarkChannelAdapter(新抽象 + 群聊支持 + PR #241 streaming 重接)。本 issue 的终点是 adapter 完整落地 + Lark inbound host path 切到新架构 + Day One 流在新架构下跑通 。出站链路的最后一公里 wire-through 转入 follow-up #294 ,以便聚焦 Day One 体验和 RFC #254 主干推进。
Deliverables (shipped in PR #288 )
Package : agents/channels/Aevatar.GAgents.Channel.Lark
Main class : LarkChannelAdapter : IChannelTransport, IChannelOutboundPort
单类同时实现两个 interface(RFC §5.4 硬约束)
迁移自现有 LarkPlatformAdapter
lifecycle / inbound stream / update / delete / capabilities / continue conversation / streaming reply 方法全部就绪
Composer : LarkMessageComposer : IMessageComposer<LarkOutboundMessage>
迁移自现有 FeishuCardHumanInteractionPort 的卡片模板
intent → Lark Interactive Card JSON(schema 2.0 body-wrapped)
新增能力(相比现状) :
群聊支持 ✅
LarkChannelAdapter 按 chat_type 构造 ConversationReference(DM → lark:dm:{open_id},Group → lark:group:{chat_id})
actor 层从 sender-keyed ChannelUserGAgent 换为 canonical-keyed ConversationGAgent(actor id = channel-conversation:{canonical_key},由 ConversationDispatchMiddleware 创建)
Streaming reply (复用 PR Stream Lark bot replies progressively as LLM deltas arrive #241 Lark 卡片 stream patch):
Webhook handler 改造 ✅:
verify signature + payload encryption(含 5min 时间戳窗口 + 明文 encrypt_key fail-closed)
顺序钉死 : verify → redact → commit to durable inbox → 200(对齐 §9.5.2)
Durable inbox:LarkConversationInboxRuntime + IStreamProvider,取代原 IMemoryCache 易失 dedup
Dedup 权威:ConversationGAgent.State.ProcessedMessageIds(cap 10000,actor 单线程串行)
PayloadRedactor.RedactAsync fail-closed(§9.6.1);抛异常返回 503
Credentials 迁移 :
Capability declaration ✅:
ChannelCapabilities.SupportsEphemeral = false / SupportsModal = false / SupportsThread = true / Streaming = Native
Acceptance
Deferred to #294
重点聚焦 Day One 体验和 RFC #254 主干,以下 wiring 不在本 issue scope 内收尾:
LarkConversationTurnRunner 出站切到 IChannelOutboundPort.BeginStreamingReplyAsync
Bot tenant_access_token 解析 / 刷新决策(直连 vs Nyx proxy,需 ADR)
ConversationGAgent turn continuation 化(LLM 生成从 actor 单线程解耦)
非 text 入站(image / file / sticker / post / rich text):要么类型化接入,要么显式声明不支持
Ephemeral disposition capability 如实上报(当前静默降级为 Normal)
Region / domain 可配置化(feishu.cn ↔ larksuite.com)
Out of scope
References
RFC §10.1 Lark (agents/channels/Aevatar.GAgents.Channel.Lark)
RFC §5.2b ConversationReference.LarkGroup
RFC §5.6 StreamingHandle
Dependencies
Parent RFC: #254
Implementation PR: #288
Wire-through follow-up: #294
Scope
从
LarkPlatformAdapter迁移到LarkChannelAdapter(新抽象 + 群聊支持 + PR #241 streaming 重接)。本 issue 的终点是 adapter 完整落地 + Lark inbound host path 切到新架构 + Day One 流在新架构下跑通。出站链路的最后一公里 wire-through 转入 follow-up #294,以便聚焦 Day One 体验和 RFC #254 主干推进。Deliverables (shipped in PR #288)
Package:
agents/channels/Aevatar.GAgents.Channel.LarkMain class:
LarkChannelAdapter : IChannelTransport, IChannelOutboundPortLarkPlatformAdapterComposer:
LarkMessageComposer : IMessageComposer<LarkOutboundMessage>FeishuCardHumanInteractionPort的卡片模板新增能力(相比现状):
LarkChannelAdapter按chat_type构造ConversationReference(DM →lark:dm:{open_id},Group →lark:group:{chat_id})ChannelUserGAgent换为 canonical-keyedConversationGAgent(actor id =channel-conversation:{canonical_key},由ConversationDispatchMiddleware创建)LarkChannelAdapter.BeginStreamingReplyAsync+LarkStreamingHandle(按SequenceNumber排序,乱序 append 正确拼接)实现完毕StreamingHandlecontract(AppendAsync(StreamChunk chunk)withSequenceNumber,CompleteAsync单次,DisposeAsync幂等兜底 interrupted 标记)LarkPlatformAdapter.SendReplyAsyncvia Nyx proxy —— user-visible streaming 由 PR Stream Lark bot replies progressively as LLM deltas arrive #241 的既有机制继续支撑,无回归。把 hostLarkConversationTurnRunner.SendReplyAsync切到IChannelOutboundPort.BeginStreamingReplyAsync的工作转入 [Channel RFC] Lark outbound + streaming wire-through on LarkChannelAdapter (post-#261) #294Webhook handler 改造 ✅:
verify → redact → commit to durable inbox → 200(对齐 §9.5.2)LarkConversationInboxRuntime+IStreamProvider,取代原IMemoryCache易失 dedupConversationGAgent.State.ProcessedMessageIds(cap 10000,actor 单线程串行)PayloadRedactor.RedactAsyncfail-closed(§9.6.1);抛异常返回 503Credentials 迁移:
encrypt_key从 proto 搬到LarkCredentialSnapshot.Parse(credential_ref)(对齐 §9.6)tenant_access_token刷新 / Nyx proxy 布线的决定转入 [Channel RFC] Lark outbound + streaming wire-through on LarkChannelAdapter (post-#261) #294(当前LarkConversationAdapterRegistry的 adapter snapshot 空access_token仅供 inbound 用,outbound 路径仍是 legacy)Capability declaration ✅:
ChannelCapabilities.SupportsEphemeral = false/SupportsModal = false/SupportsThread = true/Streaming = NativeAcceptance
LarkChannelAdapter过 Conformance Suite([Channel RFC] Conformance + Fault Injection test suite (Aevatar.GAgents.Channel.Testing) #264)Ingress_DailyReportIntent+Ingress_DailyReportSubmit+ agent-builder e2e;ConversationGAgent替代ChannelUserGAgent),无 behavior regressionIngress_GroupChat_ShouldReplyThroughConversationActor;actor idchannel-conversation:lark:group:{chat_id})StreamingHandleAPI 下跑通 —— adapter surface aligned + conformance green + 乱序 append 正确;Day One host-path wire-through 拆到 [Channel RFC] Lark outbound + streaming wire-through on LarkChannelAdapter (post-#261) #294Deferred to #294
重点聚焦 Day One 体验和 RFC #254 主干,以下 wiring 不在本 issue scope 内收尾:
LarkConversationTurnRunner出站切到IChannelOutboundPort.BeginStreamingReplyAsynctenant_access_token解析 / 刷新决策(直连 vs Nyx proxy,需 ADR)ConversationGAgentturn continuation 化(LLM 生成从 actor 单线程解耦)feishu.cn↔larksuite.com)Out of scope
LarkLongConnectionAdapter(long connection 模式)→ future sub issue,RFC §10.1.1 预留References
Dependencies