diff --git a/src/auto-reply/command-control.test.ts b/src/auto-reply/command-control.test.ts index 18a511f145c..6212a295086 100644 --- a/src/auto-reply/command-control.test.ts +++ b/src/auto-reply/command-control.test.ts @@ -877,4 +877,40 @@ describe("control command parsing", () => { }), ).toBe(true); }); + + it("detects commands wrapped in inbound metadata blocks", () => { + const metaWrapped = [ + "Conversation info (untrusted metadata):", + "```json", + '{"message_id":"msg-abc","chat_id":"chat-123"}', + "```", + "", + "/model spark", + ].join("\n"); + expect(hasControlCommand(metaWrapped)).toBe(true); + }); + + it("detects /new command after metadata prefix", () => { + const metaWrapped = [ + "Sender (untrusted metadata):", + "```json", + '{"name":"Alice","id":"user-1"}', + "```", + "", + "/new spark", + ].join("\n"); + expect(hasControlCommand(metaWrapped)).toBe(true); + }); + + it("detects /status command after timestamp + metadata prefix", () => { + const metaWrapped = [ + "[Wed 2026-03-11 23:51 PDT] Conversation info (untrusted metadata):", + "```json", + '{"chat_id":"chat-123"}', + "```", + "", + "/status", + ].join("\n"); + expect(hasControlCommand(metaWrapped)).toBe(true); + }); }); diff --git a/src/auto-reply/command-detection.ts b/src/auto-reply/command-detection.ts index f186ad771ee..bd0dd11d6f4 100644 --- a/src/auto-reply/command-detection.ts +++ b/src/auto-reply/command-detection.ts @@ -6,6 +6,7 @@ import { normalizeCommandBody, } from "./commands-registry.js"; import { isAbortTrigger } from "./reply/abort-primitives.js"; +import { stripInboundMetadata } from "./reply/strip-inbound-meta.js"; export function hasControlCommand( text?: string, @@ -19,7 +20,11 @@ export function hasControlCommand( if (!trimmed) { return false; } - const normalizedBody = normalizeCommandBody(trimmed, options); + const stripped = stripInboundMetadata(trimmed); + if (!stripped) { + return false; + } + const normalizedBody = normalizeCommandBody(stripped, options); if (!normalizedBody) { return false; } @@ -60,7 +65,8 @@ export function isControlCommandMessage( if (hasControlCommand(trimmed, cfg, options)) { return true; } - const normalized = normalizeCommandBody(trimmed, options).trim().toLowerCase(); + const stripped = stripInboundMetadata(trimmed); + const normalized = normalizeCommandBody(stripped, options).trim().toLowerCase(); return isAbortTrigger(normalized); } diff --git a/src/auto-reply/send-policy.ts b/src/auto-reply/send-policy.ts index 84dd8f83343..f86791710a8 100644 --- a/src/auto-reply/send-policy.ts +++ b/src/auto-reply/send-policy.ts @@ -1,4 +1,5 @@ import { normalizeCommandBody } from "./commands-registry.js"; +import { stripInboundMetadata } from "./reply/strip-inbound-meta.js"; export type SendPolicyOverride = "allow" | "deny"; @@ -27,7 +28,8 @@ export function parseSendPolicyCommand(raw?: string): { if (!trimmed) { return { hasCommand: false }; } - const normalized = normalizeCommandBody(trimmed); + const stripped = stripInboundMetadata(trimmed); + const normalized = normalizeCommandBody(stripped); const match = normalized.match(/^\/send(?:\s+([a-zA-Z]+))?\s*$/i); if (!match) { return { hasCommand: false };