diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 26ee43495..6103125b0 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -13,6 +13,7 @@ import type { EmbeddedContextFile } from "../pi-embedded-helpers.js"; import { buildSystemPromptParams } from "../system-prompt-params.js"; import { resolveDefaultModelForAgent } from "../model-selection.js"; import { buildAgentSystemPrompt } from "../system-prompt.js"; +import { buildTtsSystemPromptHint } from "../../tts/tts.js"; const CLI_RUN_QUEUE = new Map>(); @@ -194,6 +195,7 @@ export function buildSystemPrompt(params: { defaultModel: defaultModelLabel, }, }); + const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined; return buildAgentSystemPrompt({ workspaceDir: params.workspaceDir, defaultThinkLevel: params.defaultThinkLevel, @@ -209,6 +211,7 @@ export function buildSystemPrompt(params: { userTime, userTimeFormat, contextFiles: params.contextFiles, + ttsHint, }); } diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 771994a83..8b151a3fc 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -66,6 +66,7 @@ import { splitSdkTools } from "./tool-split.js"; import type { EmbeddedPiCompactResult } from "./types.js"; import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js"; import { describeUnknownError, mapThinkingLevel, resolveExecToolDefaults } from "./utils.js"; +import { buildTtsSystemPromptHint } from "../../tts/tts.js"; export async function compactEmbeddedPiSession(params: { sessionId: string; @@ -298,6 +299,7 @@ export async function compactEmbeddedPiSession(params: { cwd: process.cwd(), moduleUrl: import.meta.url, }); + const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined; const appendPrompt = buildEmbeddedSystemPrompt({ workspaceDir: effectiveWorkspace, defaultThinkLevel: params.thinkLevel, @@ -310,6 +312,7 @@ export async function compactEmbeddedPiSession(params: { : undefined, skillsPrompt, docsPath: docsPath ?? undefined, + ttsHint, promptMode, runtimeInfo, messageToolHints, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index c121bb42b..776525658 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -78,6 +78,7 @@ import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js"; import { buildSystemPromptParams } from "../../system-prompt-params.js"; import { describeUnknownError, mapThinkingLevel } from "../utils.js"; import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js"; +import { buildTtsSystemPromptHint } from "../../../tts/tts.js"; import { isTimeoutError } from "../../failover-error.js"; import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js"; import { MAX_IMAGE_BYTES } from "../../../media/constants.js"; @@ -315,6 +316,7 @@ export async function runEmbeddedAttempt( cwd: process.cwd(), moduleUrl: import.meta.url, }); + const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined; const appendPrompt = buildEmbeddedSystemPrompt({ workspaceDir: effectiveWorkspace, @@ -328,6 +330,7 @@ export async function runEmbeddedAttempt( : undefined, skillsPrompt, docsPath: docsPath ?? undefined, + ttsHint, workspaceNotes, reactionGuidance, promptMode, diff --git a/src/agents/pi-embedded-runner/system-prompt.ts b/src/agents/pi-embedded-runner/system-prompt.ts index cde0f0a15..33c4bfc8b 100644 --- a/src/agents/pi-embedded-runner/system-prompt.ts +++ b/src/agents/pi-embedded-runner/system-prompt.ts @@ -16,6 +16,7 @@ export function buildEmbeddedSystemPrompt(params: { heartbeatPrompt?: string; skillsPrompt?: string; docsPath?: string; + ttsHint?: string; reactionGuidance?: { level: "minimal" | "extensive"; channel: string; @@ -55,6 +56,7 @@ export function buildEmbeddedSystemPrompt(params: { heartbeatPrompt: params.heartbeatPrompt, skillsPrompt: params.skillsPrompt, docsPath: params.docsPath, + ttsHint: params.ttsHint, workspaceNotes: params.workspaceNotes, reactionGuidance: params.reactionGuidance, promptMode: params.promptMode, diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index ece7d8dcc..14f643e55 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -34,6 +34,7 @@ describe("buildAgentSystemPrompt", () => { toolNames: ["message", "memory_search"], docsPath: "/tmp/clawd/docs", extraSystemPrompt: "Subagent details", + ttsHint: "Voice (TTS) is enabled.", }); expect(prompt).not.toContain("## User Identity"); @@ -42,6 +43,7 @@ describe("buildAgentSystemPrompt", () => { expect(prompt).not.toContain("## Documentation"); expect(prompt).not.toContain("## Reply Tags"); expect(prompt).not.toContain("## Messaging"); + expect(prompt).not.toContain("## Voice (TTS)"); expect(prompt).not.toContain("## Silent Replies"); expect(prompt).not.toContain("## Heartbeats"); expect(prompt).toContain("## Subagent Context"); @@ -49,6 +51,16 @@ describe("buildAgentSystemPrompt", () => { expect(prompt).toContain("Subagent details"); }); + it("includes voice hint when provided", () => { + const prompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/clawd", + ttsHint: "Voice (TTS) is enabled.", + }); + + expect(prompt).toContain("## Voice (TTS)"); + expect(prompt).toContain("Voice (TTS) is enabled."); + }); + it("adds reasoning tag hint when enabled", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/clawd", diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 74be661aa..41ec9a7d5 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -103,6 +103,13 @@ function buildMessagingSection(params: { ]; } +function buildVoiceSection(params: { isMinimal: boolean; ttsHint?: string }) { + if (params.isMinimal) return []; + const hint = params.ttsHint?.trim(); + if (!hint) return []; + return ["## Voice (TTS)", hint, ""]; +} + function buildDocsSection(params: { docsPath?: string; isMinimal: boolean; readToolName: string }) { const docsPath = params.docsPath?.trim(); if (!docsPath || params.isMinimal) return []; @@ -137,6 +144,7 @@ export function buildAgentSystemPrompt(params: { heartbeatPrompt?: string; docsPath?: string; workspaceNotes?: string[]; + ttsHint?: string; /** Controls which hardcoded sections to include. Defaults to "full". */ promptMode?: PromptMode; runtimeInfo?: { @@ -464,6 +472,7 @@ export function buildAgentSystemPrompt(params: { runtimeChannel, messageToolHints: params.messageToolHints, }), + ...buildVoiceSection({ isMinimal, ttsHint: params.ttsHint }), ]; if (extraSystemPrompt) { diff --git a/src/auto-reply/reply/commands-context-report.ts b/src/auto-reply/reply/commands-context-report.ts index c38ae6b35..887b10722 100644 --- a/src/auto-reply/reply/commands-context-report.ts +++ b/src/auto-reply/reply/commands-context-report.ts @@ -12,6 +12,7 @@ import { buildToolSummaryMap } from "../../agents/tool-summaries.js"; import { resolveBootstrapContextForRun } from "../../agents/bootstrap-files.js"; import type { SessionSystemPromptReport } from "../../config/sessions/types.js"; import { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; +import { buildTtsSystemPromptHint } from "../../tts/tts.js"; import type { ReplyPayload } from "../types.js"; import type { HandleCommandsParams } from "./commands-types.js"; @@ -128,6 +129,7 @@ async function resolveContextReport( }, } : { enabled: false }; + const ttsHint = params.cfg ? buildTtsSystemPromptHint(params.cfg) : undefined; const systemPrompt = buildAgentSystemPrompt({ workspaceDir, @@ -145,6 +147,7 @@ async function resolveContextReport( contextFiles: injectedFiles, skillsPrompt, heartbeatPrompt: undefined, + ttsHint, runtimeInfo, sandboxInfo, }); diff --git a/src/tts/tts.ts b/src/tts/tts.ts index c89acc05c..54aa4c512 100644 --- a/src/tts/tts.ts +++ b/src/tts/tts.ts @@ -244,6 +244,19 @@ export function resolveTtsPrefsPath(config: ResolvedTtsConfig): string { return path.join(CONFIG_DIR, "settings", "tts.json"); } +export function buildTtsSystemPromptHint(cfg: ClawdbotConfig): string | undefined { + const config = resolveTtsConfig(cfg); + const prefsPath = resolveTtsPrefsPath(config); + if (!isTtsEnabled(config, prefsPath)) return undefined; + const maxLength = getTtsMaxLength(prefsPath); + const summarize = isSummarizationEnabled(prefsPath) ? "on" : "off"; + return [ + "Voice (TTS) is enabled.", + `Keep spoken text ≤${maxLength} chars to avoid auto-summary (summary ${summarize}).`, + "Use [[tts:...]] and optional [[tts:text]]...[[/tts:text]] to control voice/expressiveness.", + ].join("\n"); +} + function readPrefs(prefsPath: string): TtsUserPrefs { try { if (!existsSync(prefsPath)) return {};