mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
## ① 全面改名 HomeAgent → HologramAgent
仓库已改名,但代码与文档里仍有大量旧名。本次按"用户能看到的优先"全量替换:
- 用户可见:登录页标题/页脚、默认人格第一句、CLI 状态输出、启动日志、
plugin_install 的工具描述、AI 图片插件的 User-Agent
- 文档与脚本:README、assets/docs、docs/zh、deploy/*.sh、.github/workflows
- 鸿蒙工程:cmd/ohos/HomeAgent → cmd/ohos/HologramAgent(git mv)
目录内的 module/app 描述同步;**证书路径原样保留**
(default_HomeAgent_xxx.cer/p7b/p12 是签名材料的实际文件名,改了会找不到证书)
- 插件元数据:plugin.json 的 author
★ 必须保持原样的两处(改了会坏):
· legacySeededPromptSignature = "你是 HomeAgent 的看板娘「小宅」…"
—— 那是识别历史播种人格卡的**原文锚点**,改了就识别不出旧数据
· 公开 SDK 的 ProxyAuthHomeAgent = "homeagent"
—— 那是 SDK 契约里的值,属独立仓库,本仓不改
## ② 修 21 处乱码(U+FFFD)
这些是历史编辑留下的编码破坏,全在注释里,例如:
「赌注重下���遍」→「赌注重下一遍」
「「不��滤」」 →「「不过滤」」
「**待��退场**」→「**待退场**」
★ 保留 providers/chineseclip/tokenizer.go 里的 2 处:那是**故意**用
U+FFFD 演示"非法 UTF-8 字节被归一成 U+FFFD",不是乱码。
## ③ 提示词技术内容下沉(用户:技术性内容应该收敛到 skill 与 -help 中)
实测系统提示词 2309 字,其中两段几乎全是**模型无法据以行动的实现细节**:
【工具执行顺序】1272 字(ParallelSafe 声明、批内降级、batchRunnable 判据…)
【三层记忆】 1069 字(L1/L2/L3 编号、消费式删除、向量空间、指纹…)
收敛后 **2309 → 1723 字(-25%)**,保留的全是"模型据此做什么不同的事":
工具顺序:默认并行 / 不要依赖先后 / 同通道保序 / 写类工具可能串行
三层记忆:时序里能看最近 / doc_query 把历史事件按原始时间戳插回时序 /
关系用 memory_recall / 成文知识用 knowledge_search
判断口径写进判据:新加一段提示词前先问"模型会据此做什么不同的事",
答不上来就该进 skill 或工具描述。
## 判据
prompt_memory_test.go 新增 2 条:
- TestSystemPromptHasNoImplementationJargon
黑名单:ParallelSafe / batchRunnable / 消费式 / DenseCosine / fingerprint /
向量空间 / 批内降级 / 自激
- TestSystemPromptStaysCompact:上限 2000 字(当前 1723)
变异:把 "ParallelSafe 声明、批内降级" 加回工具顺序段 ⇒ 第一条立刻红。
## 验证
- go build ./... / go vet ./... 干净
- go test ./... -count=1:58 包 0 失败
- make check-client-versions:客户端与内核版本一致
311 lines
8.1 KiB
Go
311 lines
8.1 KiB
Go
package supervisor
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/JianFeeeee/HologramAgent/internal/network"
|
||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||
"github.com/JianFeeeee/HologramAgent/internal/tracker"
|
||
"github.com/JianFeeeee/HologramAgent/pkg/types"
|
||
)
|
||
|
||
const directAgentID types.AgentID = "main"
|
||
|
||
type Daemon struct {
|
||
cfg *types.Config
|
||
nm *network.Monitor
|
||
trk *tracker.Tracker
|
||
agents map[types.AgentID]*agentInstance
|
||
mu sync.RWMutex
|
||
ctx context.Context
|
||
cancel context.CancelFunc
|
||
heartbeatSource func(id types.AgentID) (time.Time, types.HealthStatus, error)
|
||
restartHandler func(id types.AgentID)
|
||
}
|
||
|
||
type agentInstance struct {
|
||
cfg *types.AgentConfig
|
||
state types.AgentState
|
||
health types.HealthStatus
|
||
lastHB time.Time
|
||
failCount int
|
||
useTracker bool
|
||
}
|
||
|
||
func New(cfg *types.Config) *Daemon {
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
nm := network.NewMonitor(cfg.Daemon.CheckInterval)
|
||
|
||
return &Daemon{
|
||
cfg: cfg,
|
||
nm: nm,
|
||
agents: make(map[types.AgentID]*agentInstance),
|
||
ctx: ctx,
|
||
cancel: cancel,
|
||
}
|
||
}
|
||
|
||
func (d *Daemon) SetTracker(trk *tracker.Tracker) {
|
||
d.trk = trk
|
||
}
|
||
|
||
// SetHeartbeatSource 接入 agent 真实存活探测源(direct 模式下为同进程 agent core 状态)。
|
||
// 成功返回时 lastHB 才更新;无源时 agent 健康保持未知。
|
||
func (d *Daemon) SetHeartbeatSource(fn func(id types.AgentID) (time.Time, types.HealthStatus, error)) {
|
||
d.mu.Lock()
|
||
defer d.mu.Unlock()
|
||
d.heartbeatSource = fn
|
||
}
|
||
|
||
// SetRestartHandler 接入真实重启动作(direct 模式下由 homed 注册"清理后以特殊码退出",交给 guard/systemd 重建)。
|
||
func (d *Daemon) SetRestartHandler(fn func(id types.AgentID)) {
|
||
d.mu.Lock()
|
||
defer d.mu.Unlock()
|
||
d.restartHandler = fn
|
||
}
|
||
|
||
func (d *Daemon) Start() error {
|
||
log.Println("[homed] starting HologramAgent daemon")
|
||
|
||
go d.nm.Start(d.ctx, d.cfg.Defaults.LLMEndpoints)
|
||
|
||
go d.healthLoop()
|
||
|
||
log.Println("[homed] daemon started successfully")
|
||
return nil
|
||
}
|
||
|
||
func (d *Daemon) Shutdown() {
|
||
log.Println("[homed] shutting down...")
|
||
d.cancel()
|
||
|
||
d.mu.Lock()
|
||
defer d.mu.Unlock()
|
||
|
||
for id, agent := range d.agents {
|
||
if agent.state == types.AgentStateRunning {
|
||
log.Printf("[homed] stopping agent %s", id)
|
||
agent.state = types.AgentStateStopped
|
||
}
|
||
}
|
||
}
|
||
|
||
func (d *Daemon) RegisterAgent(id types.AgentID) {
|
||
d.mu.Lock()
|
||
defer d.mu.Unlock()
|
||
|
||
useTrk := d.trk != nil
|
||
maxRetries := d.cfg.Defaults.RollbackPolicy.MaxRetries
|
||
if maxRetries <= 0 {
|
||
maxRetries = 3
|
||
}
|
||
cfg := &types.AgentConfig{ID: id}
|
||
cfg.RollbackPolicy.MaxRetries = maxRetries
|
||
|
||
d.agents[id] = &agentInstance{
|
||
cfg: cfg,
|
||
state: types.AgentStateRunning,
|
||
health: types.HealthHealthy,
|
||
lastHB: time.Now(),
|
||
useTracker: useTrk,
|
||
}
|
||
log.Printf("[homed] agent %s registered (tracker=%v, maxRetries=%d)", id, useTrk, maxRetries)
|
||
}
|
||
|
||
func (d *Daemon) healthLoop() {
|
||
interval := d.cfg.Daemon.HeartbeatInterval
|
||
if interval <= 0 {
|
||
interval = 30 * time.Second
|
||
}
|
||
ticker := time.NewTicker(interval)
|
||
defer ticker.Stop()
|
||
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
d.checkAllAgents()
|
||
case <-d.ctx.Done():
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
func (d *Daemon) checkAllAgents() {
|
||
d.mu.RLock()
|
||
agents := make(map[types.AgentID]*agentInstance)
|
||
for id, a := range d.agents {
|
||
agents[id] = a
|
||
}
|
||
d.mu.RUnlock()
|
||
|
||
for id, agent := range agents {
|
||
d.checkAgent(id, agent)
|
||
}
|
||
}
|
||
|
||
func (d *Daemon) checkAgent(id types.AgentID, agent *agentInstance) {
|
||
// 1) agent 存活源:只有确认存活才更新 lastHB
|
||
d.mu.RLock()
|
||
hbSrc := d.heartbeatSource
|
||
d.mu.RUnlock()
|
||
|
||
if hbSrc != nil {
|
||
hbTime, hbHealth, err := hbSrc(id)
|
||
if err != nil || hbTime.IsZero() {
|
||
agent.health = types.HealthDown
|
||
log.Printf("[homed] agent %s heartbeat lost: %v", id, err)
|
||
} else {
|
||
agent.lastHB = hbTime
|
||
agent.health = hbHealth
|
||
}
|
||
} else {
|
||
agent.health = types.HealthUnknown
|
||
}
|
||
|
||
// 2) 网络:仅在配置了探活端点时才据此判定降级
|
||
netStatus := d.nm.AggregateResult()
|
||
if netStatus.EndpointsConfigured && !netStatus.LLMAPIReachable {
|
||
if agent.health != types.HealthDown {
|
||
agent.health = types.HealthDegraded
|
||
}
|
||
agent.failCount++
|
||
log.Printf("[homed] agent %s: LLM API unreachable (fail %d)", id, agent.failCount)
|
||
} else if agent.health == types.HealthHealthy {
|
||
agent.failCount = 0
|
||
}
|
||
|
||
if agent.failCount >= agent.cfg.RollbackPolicy.MaxRetries {
|
||
d.handleFailure(id, agent)
|
||
}
|
||
}
|
||
|
||
func (d *Daemon) handleFailure(id types.AgentID, agent *agentInstance) {
|
||
log.Printf("[homed] agent %s failed %d times, initiating recovery", id, agent.failCount)
|
||
|
||
if agent.useTracker && d.trk != nil {
|
||
log.Printf("[homed] rolling back agent %s via change tracker", id)
|
||
if err := d.trk.Rollback(); err != nil {
|
||
log.Printf("[homed] tracker rollback failed: %v — restarting agent", err)
|
||
d.restartAgent(id, agent)
|
||
return
|
||
}
|
||
log.Printf("[homed] agent %s tracker rollback complete", id)
|
||
agent.failCount = 0
|
||
return
|
||
}
|
||
|
||
d.restartAgent(id, agent)
|
||
}
|
||
|
||
// Restart 主动请求重建本单元(**热更新入口**)。
|
||
//
|
||
// ## 为什么需要这个导出方法
|
||
//
|
||
// 「换二进制而不 systemd restart」的能力一直都存在:worker 以
|
||
// exitRestartRequested(42) 优雅退出 → guard 检测到 42 → 用**新二进制**
|
||
// 重新拉起 worker(cmd/homed/guard.go 的 workerRestartRequested 分支)。
|
||
//
|
||
// 但此前**没有任何外部办法触发它** —— restartHandler 只被
|
||
// handleFailure(健康检查失败)调用。于是"热更新"成了一项
|
||
// 只有崩溃时才生效的能力,运维实际只能 systemctl restart。
|
||
//
|
||
// 本方法把那条通道接出来:语义与"健康检查判定需要重建"完全一致,
|
||
// 但由人主动发起。
|
||
//
|
||
// ## 与 systemctl restart 的区别(为何优先用它)
|
||
//
|
||
// - 不经过 systemd,**guard 进程保持存活**(双进程架构里 guard 是父)
|
||
// - 不计入失败轮次(agent.failCount 清零)
|
||
// - 有优雅停机:停插件、flush 配置、关 tracker、sup.Shutdown()
|
||
//
|
||
// 返回错误表示"没有注册真实重启通道"——那种情况下**不会**假装成功。
|
||
func (d *Daemon) Restart(id types.AgentID) error {
|
||
d.mu.RLock()
|
||
agent, ok := d.agents[id]
|
||
d.mu.RUnlock()
|
||
if !ok {
|
||
return fmt.Errorf("agent %s 未注册", id)
|
||
}
|
||
d.restartAgent(id, agent)
|
||
return nil
|
||
}
|
||
|
||
func (d *Daemon) restartAgent(id types.AgentID, agent *agentInstance) {
|
||
agent.state = types.AgentStateStopped
|
||
agent.failCount = 0
|
||
|
||
d.mu.RLock()
|
||
handler := d.restartHandler
|
||
d.mu.RUnlock()
|
||
|
||
if handler != nil {
|
||
log.Printf("[homed] agent %s restart handler invoked", id)
|
||
handler(id)
|
||
return
|
||
}
|
||
|
||
// 无真实重启通道:退回内存态复位(记录,不再假装成功)
|
||
d.RegisterAgent(id)
|
||
agent.failCount = 0
|
||
log.Printf("[homed] agent %s reset in-memory only (no restart handler registered)", id)
|
||
}
|
||
|
||
func (d *Daemon) GetAgentStatus(id types.AgentID) (*AgentStatus, error) {
|
||
d.mu.RLock()
|
||
agent, ok := d.agents[id]
|
||
d.mu.RUnlock()
|
||
|
||
if !ok {
|
||
return nil, fmt.Errorf("agent %s not found", id)
|
||
}
|
||
|
||
netStatus := d.nm.AggregateResult()
|
||
|
||
trackerStats := map[string]interface{}{"active": false}
|
||
if d.trk != nil {
|
||
trackerStats = d.trk.Stats()
|
||
}
|
||
|
||
return &AgentStatus{
|
||
ID: id,
|
||
State: agent.state,
|
||
Health: agent.health,
|
||
Uptime: time.Since(agent.lastHB),
|
||
Network: netStatus,
|
||
TrackerStats: trackerStats,
|
||
}, nil
|
||
}
|
||
|
||
func (d *Daemon) ListAgents() []AgentStatus {
|
||
d.mu.RLock()
|
||
defer d.mu.RUnlock()
|
||
|
||
var statuses []AgentStatus
|
||
for id, agent := range d.agents {
|
||
statuses = append(statuses, AgentStatus{
|
||
ID: id,
|
||
State: agent.state,
|
||
Health: agent.health,
|
||
})
|
||
}
|
||
return statuses
|
||
}
|
||
|
||
func (d *Daemon) PreActionSnapshot(id types.AgentID) (*types.Snapshot, error) {
|
||
return nil, fmt.Errorf("snapshot not supported in direct mode — use tracker instead")
|
||
}
|
||
|
||
func (d *Daemon) RollbackAgent(id types.AgentID, snapID types.SnapshotID) error {
|
||
if d.trk != nil {
|
||
return d.trk.Rollback()
|
||
}
|
||
return fmt.Errorf("no tracker available for rollback")
|
||
}
|
||
|
||
// AgentStatus 已迁入内置 SDK,此处保留别名以兼容现有调用方。
|
||
type AgentStatus = sdk.AgentStatus
|