fix (auto-reply): expose inbound message identifiers in trusted metadata

This commit is contained in:
Vignesh Natarajan
2026-02-15 19:00:18 -08:00
parent 82333add95
commit bed8e7abe6
2 changed files with 79 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import type { TemplateContext } from "../templating.js";
import { buildInboundMetaSystemPrompt, buildInboundUserContextPrefix } from "./inbound-meta.js";
function parseInboundMetaPayload(text: string): Record<string, unknown> {
const match = text.match(/```json\n([\s\S]*?)\n```/);
if (!match?.[1]) {
throw new Error("missing inbound meta json block");
}
return JSON.parse(match[1]) as Record<string, unknown>;
}
describe("buildInboundMetaSystemPrompt", () => {
it("includes trusted message and routing ids for tool actions", () => {
const prompt = buildInboundMetaSystemPrompt({
MessageSid: "123",
MessageSidFull: "123",
ReplyToId: "99",
OriginatingTo: "telegram:5494292670",
OriginatingChannel: "telegram",
Provider: "telegram",
Surface: "telegram",
ChatType: "direct",
} as TemplateContext);
const payload = parseInboundMetaPayload(prompt);
expect(payload["schema"]).toBe("openclaw.inbound_meta.v1");
expect(payload["message_id"]).toBe("123");
expect(payload["message_id_full"]).toBeUndefined();
expect(payload["reply_to_id"]).toBe("99");
expect(payload["chat_id"]).toBe("telegram:5494292670");
expect(payload["channel"]).toBe("telegram");
});
it("keeps message_id_full only when it differs from message_id", () => {
const prompt = buildInboundMetaSystemPrompt({
MessageSid: "short-id",
MessageSidFull: "full-provider-message-id",
OriginatingTo: "channel:C1",
OriginatingChannel: "slack",
Provider: "slack",
Surface: "slack",
ChatType: "group",
} as TemplateContext);
const payload = parseInboundMetaPayload(prompt);
expect(payload["message_id"]).toBe("short-id");
expect(payload["message_id_full"]).toBe("full-provider-message-id");
});
});
describe("buildInboundUserContextPrefix", () => {
it("omits conversation label block for direct chats", () => {
const text = buildInboundUserContextPrefix({
ChatType: "direct",
ConversationLabel: "openclaw-tui",
} as TemplateContext);
expect(text).toBe("");
});
it("keeps conversation label for group chats", () => {
const text = buildInboundUserContextPrefix({
ChatType: "group",
ConversationLabel: "ops-room",
} as TemplateContext);
expect(text).toContain("Conversation info (untrusted metadata):");
expect(text).toContain('"conversation_label": "ops-room"');
});
});

View File

@@ -13,11 +13,19 @@ 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.
const payload = {
schema: "openclaw.inbound_meta.v1",
message_id: messageId,
message_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : undefined,
chat_id: chatId,
reply_to_id: replyToId,
channel: safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface) ?? safeTrim(ctx.Provider),
provider: safeTrim(ctx.Provider),
surface: safeTrim(ctx.Surface),