Background
Per PR #403 review comment, the typed Lark receive-target landed in PR #403 as flat lark_receive_id + lark_receive_id_type fields directly on platform-agnostic state messages:
UserAgentCatalogEntry
UserAgentCatalogUpsertCommand
UserAgentCatalogDocument
WorkflowAgentState / WorkflowAgentInitializedEvent / InitializeWorkflowAgentCommand
SkillRunnerOutboundConfig
These messages all also carry a platform field. Today only api-lark-bot reaches them, so the Lark coupling is fine in practice — but if Telegram / Discord / Slack are added the natural extension is per-platform field bolt-ons (telegram_chat_id, discord_channel_id, …) which violate CLAUDE.md's "核心语义强类型 / 命名语义优先" guidance.
Proposed shape
Lift the per-platform receive-target into a typed sub-message with a oneof:
message OutboundTarget {
oneof target {
LarkReceiveTarget lark = 1;
TelegramChatTarget telegram = 2;
// ...
}
}
message LarkReceiveTarget {
string receive_id = 1;
string receive_id_type = 2; // open_id / chat_id / union_id / email / user_id
}
message TelegramChatTarget {
string chat_id = 1; // ...
}
Wire as a single OutboundTarget outbound_target = N; field on each of the messages listed above; deprecate / migrate the flat lark_receive_id* fields in a follow-up cycle.
Migration considerations
- Both
UserAgentCatalogEntry.platform and the new oneof discriminator carry the platform — pick one and remove the redundancy at the same time.
UserAgentCatalogProjector.Materialize and UserAgentCatalogQueryPort.ToEntry need to round-trip the sub-message through the projection.
- Existing persisted state (Lark-only) must continue to deserialize: keep the flat fields as
reserved / sub-message-only after migration, with a one-time backfill from the flat pair.
- Trigger: do this when the second platform's actual delivery code lands (it shouldn't be added speculatively).
Out of scope
Reference
Background
Per PR #403 review comment, the typed Lark receive-target landed in PR #403 as flat
lark_receive_id+lark_receive_id_typefields directly on platform-agnostic state messages:UserAgentCatalogEntryUserAgentCatalogUpsertCommandUserAgentCatalogDocumentWorkflowAgentState/WorkflowAgentInitializedEvent/InitializeWorkflowAgentCommandSkillRunnerOutboundConfigThese messages all also carry a
platformfield. Today onlyapi-lark-botreaches them, so the Lark coupling is fine in practice — but if Telegram / Discord / Slack are added the natural extension is per-platform field bolt-ons (telegram_chat_id,discord_channel_id, …) which violate CLAUDE.md's "核心语义强类型 / 命名语义优先" guidance.Proposed shape
Lift the per-platform receive-target into a typed sub-message with a
oneof:Wire as a single
OutboundTarget outbound_target = N;field on each of the messages listed above; deprecate / migrate the flatlark_receive_id*fields in a follow-up cycle.Migration considerations
UserAgentCatalogEntry.platformand the newoneofdiscriminator carry the platform — pick one and remove the redundancy at the same time.UserAgentCatalogProjector.MaterializeandUserAgentCatalogQueryPort.ToEntryneed to round-trip the sub-message through the projection.reserved/ sub-message-only after migration, with a one-time backfill from the flat pair.Out of scope
api-lark-bot).LarkConversationTargets.Resolve(...)typed-vs-legacy fallback continues to apply for any state persisted before this refactor.Reference