diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8aeaf02..5f50da6cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai - Agents/Overflow: add Chinese context-overflow pattern detection in `isContextOverflowError` so localized provider errors route through overflow recovery paths. (#22855) Thanks @Clawborn. - Agents/Failover: treat HTTP 502/503/504 errors as failover-eligible transient timeouts so fallback chains can switch providers/models during upstream outages instead of retrying the same failing target. (#20999) Thanks @taw0002 and @vincentkoc. - Auto-reply/Inbound metadata: hide direct-chat `message_id`/`message_id_full` and sender metadata only from normalized chat type (not sender-id sentinels), preserving group metadata visibility and preventing sender-id spoofed direct-mode classification. (#24373) thanks @jd316. +- Auto-reply/Inbound metadata: move dynamic inbound `flags` (reply/forward/thread/history) from system metadata to user-context conversation info, preventing turn-by-turn prompt-cache invalidation from flag toggles. (#21785) Thanks @aidiffuser. - Auto-reply/Sessions: remove auth-key labels from `/new` and `/reset` confirmation messages so session reset notices never expose API key prefixes or env-key labels in chat output. (#24384, #24409) Thanks @Clawborn. - Slack/Group policy: move Slack account `groupPolicy` defaulting to provider-level schema defaults so multi-account configs inherit top-level `channels.slack.groupPolicy` instead of silently overriding inheritance with per-account `allowlist`. (#17579) Thanks @ZetiMente. - Providers/Anthropic: skip `context-1m-*` beta injection for OAuth/subscription tokens (`sk-ant-oat-*`) while preserving OAuth-required betas, avoiding Anthropic 401 auth failures when `params.context1m` is enabled. (#10647, #20354) Thanks @ClumsyWizardHands and @dcruver. diff --git a/src/auto-reply/reply/inbound-meta.test.ts b/src/auto-reply/reply/inbound-meta.test.ts index 239aa23bc..a85cbadab 100644 --- a/src/auto-reply/reply/inbound-meta.test.ts +++ b/src/auto-reply/reply/inbound-meta.test.ts @@ -57,6 +57,24 @@ describe("buildInboundMetaSystemPrompt", () => { expect(payload["sender_id"]).toBeUndefined(); }); + it("does not include per-turn flags in system metadata", () => { + const prompt = buildInboundMetaSystemPrompt({ + ReplyToBody: "quoted", + ForwardedFrom: "sender", + ThreadStarterBody: "starter", + InboundHistory: [{ sender: "a", body: "b", timestamp: 1 }], + WasMentioned: true, + OriginatingTo: "telegram:-1001249586642", + OriginatingChannel: "telegram", + Provider: "telegram", + Surface: "telegram", + ChatType: "group", + } as TemplateContext); + + const payload = parseInboundMetaPayload(prompt); + expect(payload["flags"]).toBeUndefined(); + }); + it("omits sender_id when blank", () => { const prompt = buildInboundMetaSystemPrompt({ MessageSid: "458", @@ -183,6 +201,25 @@ describe("buildInboundUserContextPrefix", () => { expect(conversationInfo["sender_id"]).toBe("289522496"); }); + it("includes dynamic per-turn flags in conversation info", () => { + const text = buildInboundUserContextPrefix({ + ChatType: "group", + WasMentioned: true, + ReplyToBody: "quoted", + ForwardedFrom: "sender", + ThreadStarterBody: "starter", + InboundHistory: [{ sender: "a", body: "b", timestamp: 1 }], + } as TemplateContext); + + const conversationInfo = parseConversationInfoPayload(text); + expect(conversationInfo["is_group_chat"]).toBe(true); + expect(conversationInfo["was_mentioned"]).toBe(true); + expect(conversationInfo["has_reply_context"]).toBe(true); + expect(conversationInfo["has_forwarded_context"]).toBe(true); + expect(conversationInfo["has_thread_starter"]).toBe(true); + expect(conversationInfo["history_count"]).toBe(1); + }); + it("trims sender_id in conversation info", () => { const text = buildInboundUserContextPrefix({ ChatType: "group", diff --git a/src/auto-reply/reply/inbound-meta.ts b/src/auto-reply/reply/inbound-meta.ts index 80a2d3c3c..418a42859 100644 --- a/src/auto-reply/reply/inbound-meta.ts +++ b/src/auto-reply/reply/inbound-meta.ts @@ -16,9 +16,9 @@ export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string { // 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. + // Per-message identifiers and dynamic flags are also excluded here: they change on turns/replies + // and would bust prefix-based prompt caches on providers that use stable system prefixes. + // They are included in the user-role conversation info block instead. // Resolve channel identity: prefer explicit channel, then surface, then provider. // For webchat/Hub Chat sessions (when Surface is 'webchat' or undefined with no real channel), @@ -43,14 +43,6 @@ export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string { provider: safeTrim(ctx.Provider), surface: safeTrim(ctx.Surface), chat_type: chatType ?? (isDirect ? "direct" : undefined), - flags: { - is_group_chat: !isDirect ? true : undefined, - was_mentioned: ctx.WasMentioned === true ? true : undefined, - has_reply_context: Boolean(ctx.ReplyToBody), - has_forwarded_context: Boolean(ctx.ForwardedFrom), - has_thread_starter: Boolean(safeTrim(ctx.ThreadStarterBody)), - history_count: Array.isArray(ctx.InboundHistory) ? ctx.InboundHistory.length : 0, - }, }; // Keep the instructions local to the payload so the meaning survives prompt overrides. @@ -92,7 +84,15 @@ export function buildInboundUserContextPrefix(ctx: TemplateContext): string { group_space: safeTrim(ctx.GroupSpace), thread_label: safeTrim(ctx.ThreadLabel), is_forum: ctx.IsForum === true ? true : undefined, + is_group_chat: !isDirect ? true : undefined, was_mentioned: ctx.WasMentioned === true ? true : undefined, + has_reply_context: ctx.ReplyToBody ? true : undefined, + has_forwarded_context: ctx.ForwardedFrom ? true : undefined, + has_thread_starter: safeTrim(ctx.ThreadStarterBody) ? true : undefined, + history_count: + Array.isArray(ctx.InboundHistory) && ctx.InboundHistory.length > 0 + ? ctx.InboundHistory.length + : undefined, }; if (Object.values(conversationInfo).some((v) => v !== undefined)) { blocks.push(