Files
HomeAgent/internal/agent/core/persona.go
JianFeeeee 46e014a8ca feat(persona): 首启人格门禁跨通道化 + 内核 persona_set 工具
WebUI 首启向导只覆盖 WebUI 这一条通道,而「人格该问一次」是所有通道的事:
走 QQ / CLI / ACP / 邮件来的人永远见不到那个向导,人格就永远是没确认过。

- 门禁移到 buildSystemPrompt(每轮重建 → WebUI/QQ/CLI/ACP/邮件全覆盖),
  以 core.internal.persona_initialized 为准:未确认时要求模型主动询问用户
  (默认 / 自定义 / 以后再说),确认后该段消失;personaStore 为 nil 时静默关闭。
- 新增内核内置工具 persona_set(mode=default|custom|later[, content]),
  落库逻辑与 WebUI 向导**共用 internal/config**(一个实现 + 两个薄入口:
  ConfigRegistry 直连 / 插件侧 SettingsAPI),避免两套语义各自漂移。
- AgentConfig 增加 PersonaStore 接口,cmd/homed 用 RegistryPersonaStore 实现。
- 非法输入(未知 mode / custom 空内容)在打标记**之前**拒绝:否则标记置位、
  向导被跳过,用户再没机会设。

E2E(隔离实例 + 真实 LLM 往返走 /v1/chat/completions,/var/tmp/persona/e2e.sh)9/9 PASS:
未确认时模型主动询问 → 用户答「用默认的」→ 模型调用 persona_set 落库并置位标记
→ 之后不再追问;反向对照(清标记 + 清会话上下文 + 重启)重新开始询问,
排除了「同一段对话里已问过」这一混淆。
2026-09-12 13:57:06 +08:00

55 lines
1.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"fmt"
"strings"
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
)
// PersonaStore 是人格设定的读写面。
//
// 首启门禁buildSystemPrompt与 persona_set 工具都通过它工作,实现在 cmd/homed
// 读写配置项 core.agent.personal_prompt 与一次性标记 core.internal.persona_initialized
// (落库逻辑与 WebUI 向导共用 internal/config 的实现)。
//
// 为什么放在内核而不是某个通道插件:人格是**任何通道都要问一次**的事。
// 系统提示词每轮重建门禁放在这里WebUI / QQ / CLI / ACP / 邮件等全部通道自动覆盖。
type PersonaStore interface {
// PersonaInitialized 报告人格是否已确认(向导或工具已问过)。
PersonaInitialized() bool
// SetPersona 落库人格并打一次性标记,返回是否需要重启才生效。
SetPersona(mode, content string) (restartRequired bool, err error)
}
// executePersonaTool 落地首启人格设定。
//
// 成功即打一次性标记 → 之后 buildSystemPrompt 不再要求模型询问人格。
// custom 模式返回「需重启生效」:人格在 homed 启动时载入。
func (a *Agent) executePersonaTool(tc agentAPI.ToolCall) string {
if a.personaStore == nil {
return "人格设定不可用:内核未接入配置"
}
mode, _ := tc.Arguments["mode"].(string)
content, _ := tc.Arguments["content"].(string)
mode = strings.TrimSpace(mode)
restart, err := a.personaStore.SetPersona(mode, content)
if err != nil {
return fmt.Sprintf("人格设定失败:%v", err)
}
switch mode {
case "custom":
msg := "已保存自定义人格"
if restart {
msg += "**重启 homed 后生效**(人格在启动时载入)"
}
return msg
case "default":
return "已确认使用默认人格"
case "later":
return "已记为「以后再说」,继续使用默认人格"
default:
return "已保存人格设定"
}
}