diff --git a/extensions/speech-core/src/tts.test.ts b/extensions/speech-core/src/tts.test.ts index 4d50ce1f9a0..a6acb589324 100644 --- a/extensions/speech-core/src/tts.test.ts +++ b/extensions/speech-core/src/tts.test.ts @@ -108,6 +108,7 @@ vi.mock("../api.js", async () => { const { _test, + buildTtsSystemPromptHint, getTtsPersona, getTtsProvider, maybeApplyTtsToPayload, @@ -238,6 +239,18 @@ describe("speech-core native voice-note routing", () => { expect(_test.supportsNativeVoiceNoteTts(undefined)).toBe(false); }); + it("tells generic TTS guidance to defer to MEMORY voice-delivery instructions", () => { + const hint = buildTtsSystemPromptHint(createTtsConfig("openclaw-speech-core-tts-hint-test")); + + expect(hint).toContain("Voice (TTS) is enabled."); + expect(hint).toContain( + "If workspace context (especially MEMORY.md) tells you not to use [[tts:...]] or to use a local/non-tagged voice workflow, follow that workspace instruction instead.", + ); + expect(hint).toContain( + "Use [[tts:...]] and optional [[tts:text]]...[[/tts:text]] to control voice/expressiveness.", + ); + }); + it("marks Discord auto TTS replies as native voice messages", async () => { await expectTtsPayloadResult({ channel: "discord", diff --git a/extensions/speech-core/src/tts.ts b/extensions/speech-core/src/tts.ts index af3a74c5ab6..0088279b90f 100644 --- a/extensions/speech-core/src/tts.ts +++ b/extensions/speech-core/src/tts.ts @@ -557,6 +557,7 @@ export function buildTtsSystemPromptHint( ? `Active TTS persona: ${persona.label ?? persona.id}${persona.description ? ` - ${persona.description}` : ""}.` : undefined, `Keep spoken text ≤${maxLength} chars to avoid auto-summary (summary ${summarize}).`, + "If workspace context (especially MEMORY.md) tells you not to use [[tts:...]] or to use a local/non-tagged voice workflow, follow that workspace instruction instead.", "Use [[tts:...]] and optional [[tts:text]]...[[/tts:text]] to control voice/expressiveness.", ] .filter(Boolean) diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index ff5361a3c9b..7ccbb2ddea9 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -734,6 +734,28 @@ describe("buildAgentSystemPrompt", () => { ); }); + it("adds MEMORY guidance when a memory file is present", () => { + const prompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/openclaw", + contextFiles: [ + { + path: "MEMORY.md", + content: "NEVER use [[tts:...]] or TTS commands; ALWAYS use local Piper.", + }, + ], + ttsHint: + "Voice (TTS) is enabled.\nUse [[tts:...]] and optional [[tts:text]]...[[/tts:text]] to control voice/expressiveness.", + }); + + expect(prompt).toContain( + "MEMORY.md: durable user preferences and behavior guidance. Keep following it throughout the session unless higher-priority instructions override.", + ); + expect(prompt.indexOf("NEVER use [[tts:...]]")).toBeGreaterThan(-1); + expect(prompt.lastIndexOf("## Voice (TTS)")).toBeGreaterThan( + prompt.indexOf("NEVER use [[tts:...]]"), + ); + }); + it("omits project context when no context files are injected", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/openclaw", diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 84f93346273..ca05ab8a80d 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -186,10 +186,18 @@ function buildProjectContextSection(params: { const hasSoulFile = params.files.some( (file) => getContextFileBasename(file.path) === "soul.md", ); + const hasMemoryFile = params.files.some( + (file) => getContextFileBasename(file.path) === "memory.md", + ); lines.push("The following project context files have been loaded:"); if (hasSoulFile) { lines.push("SOUL.md: persona/tone. Follow it unless higher-priority instructions override."); } + if (hasMemoryFile) { + lines.push( + "MEMORY.md: durable user preferences and behavior guidance. Keep following it throughout the session unless higher-priority instructions override.", + ); + } lines.push(""); } for (const file of params.files) {