refactor: streamline reply tag parsing
This commit is contained in:
@@ -222,6 +222,7 @@ export function buildAgentSystemPrompt(params: {
|
|||||||
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
|
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
|
||||||
"- [[reply_to_current]] replies to the triggering message.",
|
"- [[reply_to_current]] replies to the triggering message.",
|
||||||
"- [[reply_to:<id>]] replies to a specific message id when you have it.",
|
"- [[reply_to:<id>]] replies to a specific message id when you have it.",
|
||||||
|
"Whitespace inside the tag is allowed (e.g. [[ reply_to_current ]] / [[ reply_to: 123 ]]).",
|
||||||
"Tags are stripped before sending; support depends on the current provider config.",
|
"Tags are stripped before sending; support depends on the current provider config.",
|
||||||
"",
|
"",
|
||||||
"## Messaging",
|
"## Messaging",
|
||||||
|
|||||||
@@ -249,6 +249,42 @@ describe("directive behavior", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips reply tags with whitespace and maps reply_to_current to MessageSid", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
|
payloads: [{ text: "hello [[ reply_to_current ]]" }],
|
||||||
|
meta: {
|
||||||
|
durationMs: 5,
|
||||||
|
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getReplyFromConfig(
|
||||||
|
{
|
||||||
|
Body: "ping",
|
||||||
|
From: "+1004",
|
||||||
|
To: "+2000",
|
||||||
|
MessageSid: "msg-123",
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
model: "anthropic/claude-opus-4-5",
|
||||||
|
workspace: path.join(home, "clawd"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
whatsapp: { allowFrom: ["*"] },
|
||||||
|
session: { store: path.join(home, "sessions.json") },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = Array.isArray(res) ? res[0] : res;
|
||||||
|
expect(payload?.text).toBe("hello");
|
||||||
|
expect(payload?.replyToId).toBe("msg-123");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("prefers explicit reply_to id over reply_to_current", async () => {
|
it("prefers explicit reply_to id over reply_to_current", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
const REPLY_TAG_RE =
|
||||||
|
/\[\[\s*(?:reply_to_current|reply_to\s*:\s*([^\]\n]+))\s*\]\]/gi;
|
||||||
|
|
||||||
|
function normalizeReplyText(text: string) {
|
||||||
|
return text
|
||||||
|
.replace(/[ \t]+/g, " ")
|
||||||
|
.replace(/[ \t]*\n[ \t]*/g, "\n")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
export function extractReplyToTag(
|
export function extractReplyToTag(
|
||||||
text?: string,
|
text?: string,
|
||||||
currentMessageId?: string,
|
currentMessageId?: string,
|
||||||
@@ -7,29 +17,28 @@ export function extractReplyToTag(
|
|||||||
hasTag: boolean;
|
hasTag: boolean;
|
||||||
} {
|
} {
|
||||||
if (!text) return { cleaned: "", hasTag: false };
|
if (!text) return { cleaned: "", hasTag: false };
|
||||||
let cleaned = text;
|
|
||||||
let replyToId: string | undefined;
|
let sawCurrent = false;
|
||||||
|
let lastExplicitId: string | undefined;
|
||||||
let hasTag = false;
|
let hasTag = false;
|
||||||
|
|
||||||
const currentMatch = cleaned.match(/\[\[\s*reply_to_current\s*\]\]/i);
|
const cleaned = normalizeReplyText(
|
||||||
if (currentMatch) {
|
text.replace(REPLY_TAG_RE, (_full, idRaw: string | undefined) => {
|
||||||
cleaned = cleaned.replace(/\[\[\s*reply_to_current\s*\]\]/gi, " ");
|
hasTag = true;
|
||||||
hasTag = true;
|
if (idRaw === undefined) {
|
||||||
if (currentMessageId?.trim()) {
|
sawCurrent = true;
|
||||||
replyToId = currentMessageId.trim();
|
return " ";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const idMatch = cleaned.match(/\[\[\s*reply_to\s*:\s*([^\]\n]+)\s*\]\]/i);
|
const id = idRaw.trim();
|
||||||
if (idMatch?.[1]) {
|
if (id) lastExplicitId = id;
|
||||||
cleaned = cleaned.replace(/\[\[\s*reply_to\s*:\s*[^\]\n]+\s*\]\]/gi, " ");
|
return " ";
|
||||||
replyToId = idMatch[1].trim();
|
}),
|
||||||
hasTag = true;
|
);
|
||||||
}
|
|
||||||
|
const replyToId =
|
||||||
|
lastExplicitId ??
|
||||||
|
(sawCurrent ? currentMessageId?.trim() || undefined : undefined);
|
||||||
|
|
||||||
cleaned = cleaned
|
|
||||||
.replace(/[ \t]+/g, " ")
|
|
||||||
.replace(/[ \t]*\n[ \t]*/g, "\n")
|
|
||||||
.trim();
|
|
||||||
return { cleaned, replyToId, hasTag };
|
return { cleaned, replyToId, hasTag };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user