feat: Friend 消息 → 仅使用人设提示词
Browse files修改的文件 (5个)
核心流程:Friend 消息 → 仅使用人设提示词
1. src/types/textInputTypes.ts
- 在 QueuedCommand 中添加 overrideSystemPrompt?: string 字段,用于存储替代系统提示词
2. src/skills/bundled/friendPrompt.ts
- 导出 loadPersona() 和 buildVrmSystemPrompt(),供 FriendService 调用
3. src/friend/FriendService.ts
- 在 sendText() 中构建 VRM 人设提示词,作为 overrideSystemPrompt 随消息入队
4. src/utils/handlePromptSubmit.ts
- 在 onQuery 类型中添加 overrideSystemPrompt 参数
- 从 commands[0] 提取并传递给 onQuery
5. src/screens/REPL.tsx
- onQuery 和 onQueryImpl 均接收 overrideSystemPrompt 参数
- 传入 buildEffectiveSystemPrompt(),当该值存在时完全替代默认系统提示词
数据流
FriendService.sendText()
→ buildVrmSystemPrompt() ← 只构建 VRM 人设提示词
→ enqueue({ overrideSystemPrompt, ... })
→ processQueueIfReady()
→ handlePromptSubmit()
→ executeUserInput()
→ onQuery(..., overrideSystemPrompt)
→ onQueryImpl(..., overrideSystemPrompt)
→ buildEffectiveSystemPrompt({ overrideSystemPrompt })
→ 返回 [overrideSystemPrompt] ← 完全绕过 ~900 行 VersperClaw CLI 提示词
现在 /friend 模式下的消息只会使用人设提示词(VRM 情绪工具指令 + IDENTITY.md + SOUL.md),不再注入 VersperClaw
的编程助手系统提示词。
|
@@ -21,6 +21,7 @@ import { edgeTts, qwenTts, registerAudioFile, getAudioFile } from './tts.js';
|
|
| 21 |
import { splitSentences } from './text-utils.js';
|
| 22 |
import { SileroVad } from './voice/vad-service.js';
|
| 23 |
import { readFileSync } from 'node:fs';
|
|
|
|
| 24 |
|
| 25 |
// ── Types ──────────────────────────────────────────────────────────────
|
| 26 |
|
|
@@ -169,6 +170,9 @@ class FriendService {
|
|
| 169 |
listener({ text: trimmed });
|
| 170 |
}
|
| 171 |
|
|
|
|
|
|
|
|
|
|
| 172 |
// Dynamically import enqueue to avoid circular deps
|
| 173 |
import('../utils/messageQueueManager.js').then(({ enqueue }) => {
|
| 174 |
enqueue({
|
|
@@ -177,6 +181,7 @@ class FriendService {
|
|
| 177 |
skipSlashCommands: true,
|
| 178 |
bridgeOrigin: true,
|
| 179 |
origin: { kind: 'channel', server: 'friend' },
|
|
|
|
| 180 |
});
|
| 181 |
}).catch((err) => {
|
| 182 |
console.error('[FriendService] enqueue failed:', err);
|
|
|
|
| 21 |
import { splitSentences } from './text-utils.js';
|
| 22 |
import { SileroVad } from './voice/vad-service.js';
|
| 23 |
import { readFileSync } from 'node:fs';
|
| 24 |
+
import { buildVrmSystemPrompt } from '../skills/bundled/friendPrompt.js';
|
| 25 |
|
| 26 |
// ── Types ──────────────────────────────────────────────────────────────
|
| 27 |
|
|
|
|
| 170 |
listener({ text: trimmed });
|
| 171 |
}
|
| 172 |
|
| 173 |
+
// Build the override system prompt (persona-only, no VersperClaw CLI prompt)
|
| 174 |
+
const overrideSystemPrompt = buildVrmSystemPrompt();
|
| 175 |
+
|
| 176 |
// Dynamically import enqueue to avoid circular deps
|
| 177 |
import('../utils/messageQueueManager.js').then(({ enqueue }) => {
|
| 178 |
enqueue({
|
|
|
|
| 181 |
skipSlashCommands: true,
|
| 182 |
bridgeOrigin: true,
|
| 183 |
origin: { kind: 'channel', server: 'friend' },
|
| 184 |
+
overrideSystemPrompt,
|
| 185 |
});
|
| 186 |
}).catch((err) => {
|
| 187 |
console.error('[FriendService] enqueue failed:', err);
|
|
@@ -3565,6 +3565,7 @@ export function REPL({
|
|
| 3565 |
additionalAllowedTools: string[],
|
| 3566 |
mainLoopModelParam: string,
|
| 3567 |
effort?: EffortValue,
|
|
|
|
| 3568 |
) => {
|
| 3569 |
// Prepare IDE integration for new prompt. Read mcpClients fresh from
|
| 3570 |
// store — useManageMCPConnections may have populated it since the
|
|
@@ -3753,6 +3754,7 @@ export function REPL({
|
|
| 3753 |
customSystemPrompt,
|
| 3754 |
defaultSystemPrompt,
|
| 3755 |
appendSystemPrompt,
|
|
|
|
| 3756 |
})
|
| 3757 |
toolUseContext.renderedSystemPrompt = systemPrompt
|
| 3758 |
|
|
@@ -3867,6 +3869,7 @@ export function REPL({
|
|
| 3867 |
) => Promise<boolean>,
|
| 3868 |
input?: string,
|
| 3869 |
effort?: EffortValue,
|
|
|
|
| 3870 |
): Promise<void> => {
|
| 3871 |
// If this is a teammate, mark them as active when starting a turn
|
| 3872 |
if (isAgentSwarmsEnabled()) {
|
|
@@ -3947,6 +3950,7 @@ export function REPL({
|
|
| 3947 |
additionalAllowedTools,
|
| 3948 |
mainLoopModelParam,
|
| 3949 |
effort,
|
|
|
|
| 3950 |
)
|
| 3951 |
} finally {
|
| 3952 |
// queryGuard.end() atomically checks generation and transitions
|
|
|
|
| 3565 |
additionalAllowedTools: string[],
|
| 3566 |
mainLoopModelParam: string,
|
| 3567 |
effort?: EffortValue,
|
| 3568 |
+
overrideSystemPrompt?: string,
|
| 3569 |
) => {
|
| 3570 |
// Prepare IDE integration for new prompt. Read mcpClients fresh from
|
| 3571 |
// store — useManageMCPConnections may have populated it since the
|
|
|
|
| 3754 |
customSystemPrompt,
|
| 3755 |
defaultSystemPrompt,
|
| 3756 |
appendSystemPrompt,
|
| 3757 |
+
overrideSystemPrompt,
|
| 3758 |
})
|
| 3759 |
toolUseContext.renderedSystemPrompt = systemPrompt
|
| 3760 |
|
|
|
|
| 3869 |
) => Promise<boolean>,
|
| 3870 |
input?: string,
|
| 3871 |
effort?: EffortValue,
|
| 3872 |
+
overrideSystemPrompt?: string,
|
| 3873 |
): Promise<void> => {
|
| 3874 |
// If this is a teammate, mark them as active when starting a turn
|
| 3875 |
if (isAgentSwarmsEnabled()) {
|
|
|
|
| 3950 |
additionalAllowedTools,
|
| 3951 |
mainLoopModelParam,
|
| 3952 |
effort,
|
| 3953 |
+
overrideSystemPrompt,
|
| 3954 |
)
|
| 3955 |
} finally {
|
| 3956 |
// queryGuard.end() atomically checks generation and transitions
|
|
@@ -13,7 +13,7 @@ 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[] = []
|
|
@@ -37,7 +37,7 @@ function loadPersona(): string {
|
|
| 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 |
|
|
|
|
| 13 |
|
| 14 |
const FRIEND_URL = 'http://127.0.0.1:3456/friend/'
|
| 15 |
|
| 16 |
+
export 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[] = []
|
|
|
|
| 37 |
return parts.join('\n\n')
|
| 38 |
}
|
| 39 |
|
| 40 |
+
export function buildVrmSystemPrompt(): string {
|
| 41 |
const prefs = getPrefs()
|
| 42 |
const moodIndex = (prefs as any)._moodIndex ?? 60
|
| 43 |
|
|
@@ -355,6 +355,12 @@ export type QueuedCommand = {
|
|
| 355 |
* unified the queue but lost the isolation the dual-queue accidentally had).
|
| 356 |
*/
|
| 357 |
agentId?: AgentId
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
}
|
| 359 |
|
| 360 |
/**
|
|
|
|
| 355 |
* unified the queue but lost the isolation the dual-queue accidentally had).
|
| 356 |
*/
|
| 357 |
agentId?: AgentId
|
| 358 |
+
/**
|
| 359 |
+
* When set, completely replaces the default system prompt for this query.
|
| 360 |
+
* Used by Friend mode to use ONLY the persona prompt instead of the full
|
| 361 |
+
* VersperClaw CLI system prompt.
|
| 362 |
+
*/
|
| 363 |
+
overrideSystemPrompt?: string
|
| 364 |
}
|
| 365 |
|
| 366 |
/**
|
|
@@ -69,6 +69,7 @@ type BaseExecutionParams = {
|
|
| 69 |
onBeforeQuery?: (input: string, newMessages: Message[]) => Promise<boolean>,
|
| 70 |
input?: string,
|
| 71 |
effort?: EffortValue,
|
|
|
|
| 72 |
) => Promise<void>
|
| 73 |
setAppState: (updater: (prev: AppState) => AppState) => void
|
| 74 |
onBeforeQuery?: (input: string, newMessages: Message[]) => Promise<boolean>
|
|
@@ -556,6 +557,7 @@ async function executeUserInput(params: ExecuteUserInputParams): Promise<void> {
|
|
| 556 |
primaryCmd && typeof primaryCmd.value === 'string'
|
| 557 |
? primaryCmd.value
|
| 558 |
: undefined
|
|
|
|
| 559 |
const shouldCallBeforeQuery = primaryMode === 'prompt'
|
| 560 |
await onQuery(
|
| 561 |
newMessages,
|
|
@@ -568,6 +570,7 @@ async function executeUserInput(params: ExecuteUserInputParams): Promise<void> {
|
|
| 568 |
shouldCallBeforeQuery ? onBeforeQuery : undefined,
|
| 569 |
primaryInput,
|
| 570 |
effort,
|
|
|
|
| 571 |
)
|
| 572 |
} else {
|
| 573 |
// Local slash commands that skip messages (e.g., /model, /theme).
|
|
|
|
| 69 |
onBeforeQuery?: (input: string, newMessages: Message[]) => Promise<boolean>,
|
| 70 |
input?: string,
|
| 71 |
effort?: EffortValue,
|
| 72 |
+
overrideSystemPrompt?: string,
|
| 73 |
) => Promise<void>
|
| 74 |
setAppState: (updater: (prev: AppState) => AppState) => void
|
| 75 |
onBeforeQuery?: (input: string, newMessages: Message[]) => Promise<boolean>
|
|
|
|
| 557 |
primaryCmd && typeof primaryCmd.value === 'string'
|
| 558 |
? primaryCmd.value
|
| 559 |
: undefined
|
| 560 |
+
const overrideSystemPrompt = commands[0]?.overrideSystemPrompt
|
| 561 |
const shouldCallBeforeQuery = primaryMode === 'prompt'
|
| 562 |
await onQuery(
|
| 563 |
newMessages,
|
|
|
|
| 570 |
shouldCallBeforeQuery ? onBeforeQuery : undefined,
|
| 571 |
primaryInput,
|
| 572 |
effort,
|
| 573 |
+
overrideSystemPrompt,
|
| 574 |
)
|
| 575 |
} else {
|
| 576 |
// Local slash commands that skip messages (e.g., /model, /theme).
|