📨 接收 · 归一化 Receive & Normalize
把五花八门的平台事件,先压成一条平台中立的消息。
消息从 Telegram、Slack、微信还是 Discord 进来并不重要——openclaw 先把原始平台事件包进一个 MessageReceiveContext,它不解析语义,只管三件事:给这次接收一个唯一 id、持有原始消息、维护一个「何时才告诉平台我收到了」的 ack 状态机。
紧接着是去重(同一条消息因重试被投递两次怎么办)和分类(这是要回复的请求,还是群里一句没 @ 你的闲聊)。只有「该回复」的消息才会继续往下走。
真实代码 receive.ts:1CODE
export type MessageReceiveContext<TMessage = unknown> = {
id: string;
channel: string;
accountId?: string;
message: TMessage;
ackPolicy: MessageAckPolicy;
ackState: MessageAckState;
signal: AbortSignal;
shouldAckAfter(stage: MessageAckStage): boolean;
ack(): Promise<void>;
nack(error: unknown): Promise<void>;
};ack 状态机是可配置的延迟确认契约——「先处理再确认」还是「先确认再处理」由 ackPolicy 决定,让每个平台都能按自己的可靠性语义接入,而不必各自重写幂等逻辑。
真实代码 · 分类 classification.ts:15CODE
export function classifyChannelInboundEvent(
params: ClassifyChannelInboundEventParams,
): InboundEventKind {
if (params.unmentionedGroupPolicy !== "room_event") {
return "user_request";
}
if (params.conversation.kind !== "group" && params.conversation.kind !== "channel") {
return "user_request";
}
if (
params.wasMentioned === true ||
params.hasControlCommand === true ||
params.commandSource === "native"
) {
return "user_request";
}
return "room_event";
}群里没 @ 你的消息被归为 room_event——它是个正式类型而非布尔值,意味着系统把「未被点名的群聊消息」当一等公民保留,将来可以为它单独定制处理策略(比如只更新上下文、不触发模型)。
实际文本 · 消息信封 formatAgentEnvelopePROMPT
[Telegram Pash 2026-05-31 10:23] Pash: Can you check whether the nightly build finished and tell me what happened?
[渠道 Telegram|发送者 Pash|时间 2026-05-31 10:23] Pash:帮我看看昨晚的构建跑完了没,出了什么情况?