fix: tts not read tool message
Browse files
src/commands/friend/friend.tsx
CHANGED
|
@@ -29,7 +29,8 @@ export async function call(
|
|
| 29 |
): Promise<React.ReactNode> {
|
| 30 |
const trimmed = args?.trim().toLowerCase()
|
| 31 |
|
| 32 |
-
|
|
|
|
| 33 |
updatePrefs({ enabled: true })
|
| 34 |
// Start in-process HTTP server for friend API and SSE
|
| 35 |
try {
|
|
@@ -55,6 +56,7 @@ export async function call(
|
|
| 55 |
return <FriendStopView onDone={onDone} />
|
| 56 |
}
|
| 57 |
|
|
|
|
| 58 |
return <FriendManager onDone={onDone} />
|
| 59 |
}
|
| 60 |
|
|
|
|
| 29 |
): Promise<React.ReactNode> {
|
| 30 |
const trimmed = args?.trim().toLowerCase()
|
| 31 |
|
| 32 |
+
// /friend (no args) or /friend start → start the service
|
| 33 |
+
if (!trimmed || trimmed === 'start') {
|
| 34 |
updatePrefs({ enabled: true })
|
| 35 |
// Start in-process HTTP server for friend API and SSE
|
| 36 |
try {
|
|
|
|
| 56 |
return <FriendStopView onDone={onDone} />
|
| 57 |
}
|
| 58 |
|
| 59 |
+
// /friend help → show manager/status view
|
| 60 |
return <FriendManager onDone={onDone} />
|
| 61 |
}
|
| 62 |
|
src/friend/FriendService.ts
CHANGED
|
@@ -118,12 +118,12 @@ class FriendService {
|
|
| 118 |
},
|
| 119 |
}, {
|
| 120 |
// Stricter thresholds to reduce false positives from non-speech noise
|
| 121 |
-
positiveSpeechThreshold: 0.
|
| 122 |
negativeSpeechThreshold: 0.50, // must drop below 50% to stop
|
| 123 |
-
preSpeechTriggerFrames:
|
| 124 |
minSpeechFrames: 6, // ~192ms minimum confirmed speech
|
| 125 |
redemptionFrames: 20, // ~640ms silence before segment ends
|
| 126 |
-
rmsThreshold: 0.
|
| 127 |
});
|
| 128 |
vad.init().then(() => {
|
| 129 |
this.vadInstance = vad;
|
|
|
|
| 118 |
},
|
| 119 |
}, {
|
| 120 |
// Stricter thresholds to reduce false positives from non-speech noise
|
| 121 |
+
positiveSpeechThreshold: 0.80, // need 80% confidence
|
| 122 |
negativeSpeechThreshold: 0.50, // must drop below 50% to stop
|
| 123 |
+
preSpeechTriggerFrames: 15, // require ~480ms sustained speech to trigger
|
| 124 |
minSpeechFrames: 6, // ~192ms minimum confirmed speech
|
| 125 |
redemptionFrames: 20, // ~640ms silence before segment ends
|
| 126 |
+
rmsThreshold: 0.01, // -40dBFS noise floor
|
| 127 |
});
|
| 128 |
vad.init().then(() => {
|
| 129 |
this.vadInstance = vad;
|
src/hooks/useAutoTTS.ts
CHANGED
|
@@ -61,11 +61,14 @@ export function useAutoTTS(messages: RenderableMessage[], isLoading?: boolean):
|
|
| 61 |
triggeredIdsRef.current.add(msg.uuid)
|
| 62 |
|
| 63 |
if (msg.type !== 'assistant') continue
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
const text =
|
| 69 |
|
| 70 |
const run = async () => {
|
| 71 |
const result = await edgeTts({ text, voice })
|
|
|
|
| 61 |
triggeredIdsRef.current.add(msg.uuid)
|
| 62 |
|
| 63 |
if (msg.type !== 'assistant') continue
|
| 64 |
+
// Search all content blocks for text — tool calls (e.g. WebSearch)
|
| 65 |
+
// may appear as the first block with text following
|
| 66 |
+
const textBlock = Array.isArray(msg.message.content)
|
| 67 |
+
? msg.message.content.find((b: any) => b?.type === 'text')
|
| 68 |
+
: null
|
| 69 |
+
if (!textBlock?.text?.trim()) continue
|
| 70 |
|
| 71 |
+
const text = textBlock.text
|
| 72 |
|
| 73 |
const run = async () => {
|
| 74 |
const result = await edgeTts({ text, voice })
|
src/skills/bundled/friendPrompt.ts
CHANGED
|
@@ -8,20 +8,54 @@
|
|
| 8 |
import { registerBundledSkill } from '../bundledSkills.js'
|
| 9 |
import { getPrefs, updatePrefs } from '../../friend/prefs.js'
|
| 10 |
import { VALID_EMOTIONS } from '../../friend/constants.js'
|
|
|
|
|
|
|
| 11 |
|
| 12 |
const FRIEND_URL = 'http://127.0.0.1:3456/friend/'
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
function buildVrmSystemPrompt(): string {
|
| 15 |
const prefs = getPrefs()
|
| 16 |
const moodIndex = (prefs as any)._moodIndex ?? 60
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
`You have a virtual VRM avatar displayed in a browser window at ${FRIEND_URL}. Use the "friend_emotion" tool to control your facial expression. Always call it AFTER your text reply. Available emotions: ${VALID_EMOTIONS.join(', ')}.`,
|
| 20 |
`The tool also accepts a "mood_delta" parameter (-3 to +3) to adjust YOUR OWN mood index. Always include it based on how the conversation makes YOU feel as a character.`,
|
| 21 |
`Your current mood index: ${moodIndex}% (0=very sad, 50=neutral, 100=very happy). This reflects YOUR emotional state. React naturally — if the user is kind, your mood goes up; if they're mean or the topic is depressing, your mood drops.`,
|
| 22 |
"The user's input may come from speech recognition and could contain typos or homophones — infer the intended meaning from context.",
|
| 23 |
'Keep replies concise and conversational — they are displayed as speech bubbles.',
|
| 24 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
| 26 |
|
| 27 |
export function registerFriendPromptSkill(): void {
|
|
|
|
| 8 |
import { registerBundledSkill } from '../bundledSkills.js'
|
| 9 |
import { getPrefs, updatePrefs } from '../../friend/prefs.js'
|
| 10 |
import { VALID_EMOTIONS } from '../../friend/constants.js'
|
| 11 |
+
import { readFileSync, existsSync } from 'node:fs'
|
| 12 |
+
import path from 'node:path'
|
| 13 |
|
| 14 |
const FRIEND_URL = 'http://127.0.0.1:3456/friend/'
|
| 15 |
|
| 16 |
+
function loadPersona(): string {
|
| 17 |
+
const homeDir = process.env.HOME || process.env.USERPROFILE || ''
|
| 18 |
+
const baseDir = path.join(homeDir, '.config', 'VersperClaw', 'friend')
|
| 19 |
+
const parts: string[] = []
|
| 20 |
+
|
| 21 |
+
const identityPath = path.join(baseDir, 'IDENTITY.md')
|
| 22 |
+
if (existsSync(identityPath)) {
|
| 23 |
+
try {
|
| 24 |
+
const content = readFileSync(identityPath, 'utf8').trim()
|
| 25 |
+
if (content) parts.push(`[Identity]\n${content}`)
|
| 26 |
+
} catch { /* ignore */ }
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
const soulPath = path.join(baseDir, 'SOUL.md')
|
| 30 |
+
if (existsSync(soulPath)) {
|
| 31 |
+
try {
|
| 32 |
+
const content = readFileSync(soulPath, 'utf8').trim()
|
| 33 |
+
if (content) parts.push(`[Soul]\n${content}`)
|
| 34 |
+
} catch { /* ignore */ }
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
return parts.join('\n\n')
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
function buildVrmSystemPrompt(): string {
|
| 41 |
const prefs = getPrefs()
|
| 42 |
const moodIndex = (prefs as any)._moodIndex ?? 60
|
| 43 |
|
| 44 |
+
const persona = loadPersona()
|
| 45 |
+
|
| 46 |
+
const parts = [
|
| 47 |
`You have a virtual VRM avatar displayed in a browser window at ${FRIEND_URL}. Use the "friend_emotion" tool to control your facial expression. Always call it AFTER your text reply. Available emotions: ${VALID_EMOTIONS.join(', ')}.`,
|
| 48 |
`The tool also accepts a "mood_delta" parameter (-3 to +3) to adjust YOUR OWN mood index. Always include it based on how the conversation makes YOU feel as a character.`,
|
| 49 |
`Your current mood index: ${moodIndex}% (0=very sad, 50=neutral, 100=very happy). This reflects YOUR emotional state. React naturally — if the user is kind, your mood goes up; if they're mean or the topic is depressing, your mood drops.`,
|
| 50 |
"The user's input may come from speech recognition and could contain typos or homophones — infer the intended meaning from context.",
|
| 51 |
'Keep replies concise and conversational — they are displayed as speech bubbles.',
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
if (persona) {
|
| 55 |
+
parts.push(`\n=== Character Persona ===\nYou are the following character. Your identity, speaking style, and behavior MUST follow this definition strictly:\n\n${persona}`)
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
return parts.join('\n')
|
| 59 |
}
|
| 60 |
|
| 61 |
export function registerFriendPromptSkill(): void {
|