fix(auto-reply): restore prompt cache stability by moving per-turn ids to user context (#20597)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 175919afb6
Co-authored-by: anisoptera <768771+anisoptera@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
Isis Anisoptera
2026-02-19 11:11:47 -08:00
committed by GitHub
parent ff3a7e5635
commit 4b7d89100e
3 changed files with 79 additions and 64 deletions

View File

@@ -13,20 +13,15 @@ function safeTrim(value: unknown): string | undefined {
export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string {
const chatType = normalizeChatType(ctx.ChatType);
const isDirect = !chatType || chatType === "direct";
const messageId = safeTrim(ctx.MessageSid);
const messageIdFull = safeTrim(ctx.MessageSidFull);
const replyToId = safeTrim(ctx.ReplyToId);
const chatId = safeTrim(ctx.OriginatingTo);
// Keep system metadata strictly free of attacker-controlled strings (sender names, group subjects, etc.).
// Those belong in the user-role "untrusted context" blocks.
// Per-message identifiers (message_id, reply_to_id, sender_id) are also excluded here: they change
// on every turn and would bust prefix-based prompt caches on local model providers. They are
// included in the user-role conversation info block via buildInboundUserContextPrefix() instead.
const payload = {
schema: "openclaw.inbound_meta.v1",
message_id: messageId,
message_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : undefined,
sender_id: safeTrim(ctx.SenderId),
chat_id: chatId,
reply_to_id: replyToId,
chat_id: safeTrim(ctx.OriginatingTo),
channel: safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface) ?? safeTrim(ctx.Provider),
provider: safeTrim(ctx.Provider),
surface: safeTrim(ctx.Surface),
@@ -60,8 +55,13 @@ export function buildInboundUserContextPrefix(ctx: TemplateContext): string {
const chatType = normalizeChatType(ctx.ChatType);
const isDirect = !chatType || chatType === "direct";
const messageId = safeTrim(ctx.MessageSid);
const messageIdFull = safeTrim(ctx.MessageSidFull);
const conversationInfo = {
message_id: safeTrim(ctx.MessageSid),
message_id: messageId,
message_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : undefined,
reply_to_id: safeTrim(ctx.ReplyToId),
sender_id: safeTrim(ctx.SenderId),
conversation_label: isDirect ? undefined : safeTrim(ctx.ConversationLabel),
sender: safeTrim(ctx.SenderE164) ?? safeTrim(ctx.SenderId) ?? safeTrim(ctx.SenderUsername),
group_subject: safeTrim(ctx.GroupSubject),