mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 09:17:31 +00:00
refactor: 全面改名为 HologramAgent + 提示词技术内容下沉到 skill/工具描述 + 修 21 处乱码
## ① 全面改名 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:客户端与内核版本一致
This commit is contained in:
@ -286,7 +286,7 @@ func (a *Agent) toolParallelSafe(name string) bool {
|
||||
//
|
||||
// 走过的弯路(都留在注释里,因为每一种都"看起来能工作"):
|
||||
// 1. 内核里一张 map[string]bool 硬编码名单:声明从工具搬回内核,
|
||||
// 工具改名/新增不会跟着变,要靠 grep 源码的判据才<EFBFBD><EFBFBD><EFBFBD>发现漂移;
|
||||
// 工具改名/新增不会跟着变,要靠 grep 源码的判据才能发现漂移;
|
||||
// 2. 往 required 变参里塞字符串 "toolParallel":拼错静默失效,
|
||||
// 编译器不报错,而"少一个工具能并发"正是最难察觉的那类问题;
|
||||
// 3. 每次查询重跑 buildToolDefs():正确但 O(工具数) 重复劳动。
|
||||
|
||||
@ -17,7 +17,7 @@ import (
|
||||
//
|
||||
// 注入必须**晚绑定**且**per-agent**:内置工具的可见性由运行期状态门控
|
||||
// (`if a.memory != nil` / `if a.knowledge != nil` …),而驻留子是轻量内核、
|
||||
// memory 为 nil。<EFBFBD><EFBFBD> ToolAPI 是全局单例,拿不到 agent,只能由 agent 自己
|
||||
// memory 为 nil。而 ToolAPI 是全局单例,拿不到 agent,只能由 agent 自己
|
||||
// 提供一份 provider。
|
||||
|
||||
// builtinProvider 是 *Agent 上的适配器:把 agent 的内置工具面
|
||||
|
||||
@ -21,7 +21,7 @@ type fakeOverflowProvider struct {
|
||||
|
||||
func (p *fakeOverflowProvider) MaxContextTokens() int { return p.window }
|
||||
|
||||
// newOverflowAgent 造一个够用<EFBFBD><EFBFBD><EFBFBD>超页测试 Agent。
|
||||
// newOverflowAgent 造一个够用的超页测试 Agent。
|
||||
func newOverflowAgent(window, maxContextSize int, events int, chunkChars int) *Agent {
|
||||
a := &Agent{
|
||||
io: agentIO.NewIOManager(),
|
||||
@ -237,7 +237,7 @@ func TestOverflow_T5_恢复预算耗尽则终止(t *testing.T) {
|
||||
t.Fatalf("首次应正常裁剪:handled=%v pruned=%d", handled, pruned)
|
||||
}
|
||||
if f.OverflowRecover != 1 {
|
||||
t.Fatalf("首次应记一次恢复,<EFBFBD><EFBFBD>际 %d", f.OverflowRecover)
|
||||
t.Fatalf("首次应记一次恢复,实际 %d", f.OverflowRecover)
|
||||
}
|
||||
|
||||
// 制造「裁不动」:每次检查前把上下文压到只剩 1 条大事件
|
||||
@ -524,7 +524,7 @@ func TestOverflow_protected按预算收紧(t *testing.T) {
|
||||
// 大窗口 ⇒ 用满配置值(这正是「1M 下这套调度器能更好」的机制)
|
||||
big := newOverflowAgent(1_000_000, 30, 12, 200)
|
||||
if got := big.effectiveProtectedCount(); got != big.context.ProtectedCount() {
|
||||
t.Fatalf("大窗口下应<EFBFBD><EFBFBD>满配置值 %d,实际 %d", big.context.ProtectedCount(), got)
|
||||
t.Fatalf("大窗口下应达满配置值 %d,实际 %d", big.context.ProtectedCount(), got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -168,7 +168,7 @@ func (a *Agent) executeGetPluginTools(pluginName string) string {
|
||||
}
|
||||
// ★★ 收集**内置工具**(2026-05-05)。
|
||||
//
|
||||
// 原实<EFBFBD><EFBFBD><EFBFBD>只收 StageHost(SDK 插件) 与 IOManager(设备) 两处,
|
||||
// 原实现只收 StageHost(SDK 插件) 与 IOManager(设备) 两处,
|
||||
// 而 describe_image / ocr_image / memory_recall / knowledge_* 等
|
||||
// 都是内核 buildToolDefs 里直接 append 的 ⇒ **一条都数不到**。
|
||||
// 症状:agent 能调这些工具,却调 get_plugin_tools("") 看不到它们,
|
||||
|
||||
@ -190,3 +190,53 @@ func TestPrefillMarkdownCarriesContentAndGuide(t *testing.T) {
|
||||
t.Errorf("预填充文案没有说明来源层:\n%s", md)
|
||||
}
|
||||
}
|
||||
|
||||
// ⑥ 技术性内容不得留在系统提示词里(收敛到 skill / 工具描述)。
|
||||
//
|
||||
// 背景(用户 2026-10-22):agent 说话人机味重。查明主因不是性格设定,
|
||||
// 而是系统提示词里堆了**模型无法据以行动的实现细节**:
|
||||
//
|
||||
// 【工具执行顺序】1272 字(ParallelSafe 声明、批内降级、内核判据…)
|
||||
// 【三层记忆】 1069 字(L1/L2/L3 编号、消费式删除、向量空间…)
|
||||
//
|
||||
// 合计占提示词的绝大部分,而且全是"说明书腔"。模型学不到怎么用它,
|
||||
// 却会照着这种语气说话。收敛后两段合计不到 500 字。
|
||||
//
|
||||
// 本判据钉住:这些术语不得回到系统提示词。
|
||||
func TestSystemPromptHasNoImplementationJargon(t *testing.T) {
|
||||
a := newToolTestAgent(t)
|
||||
prompt := a.buildSystemPrompt("", "测试输入")
|
||||
|
||||
jargon := []string{
|
||||
"ParallelSafe", // 工具声明项,模型不判断也改不了
|
||||
"batchRunnable", // 内核内部函数名
|
||||
"消费式", // L2 取出语义的实现说法
|
||||
"DenseCosine", // 算法名
|
||||
"fingerprint", // 向量空间标识
|
||||
"向量空间", // 实现概念
|
||||
"批内降级", // 内核调度细节
|
||||
"自激", // 设计隐患,模型无需知道
|
||||
}
|
||||
for _, j := range jargon {
|
||||
if strings.Contains(prompt, j) {
|
||||
t.Errorf("系统提示词含实现术语 %q —— 技术内容应下沉到 skill / 工具描述,\n"+
|
||||
"留在这里只会让回复带上说明书腔,且模型无法据此行动", j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ⑦ 提示词长度上限(防止技术说明再次淤积)。
|
||||
//
|
||||
// 当前实测 1723 字(收敛前 2309)。上限取 2000:留有增删余地,
|
||||
// 但堵住"又加一大段实现说明"这种回潮。
|
||||
func TestSystemPromptStaysCompact(t *testing.T) {
|
||||
a := newToolTestAgent(t)
|
||||
prompt := a.buildSystemPrompt("", "测试输入")
|
||||
|
||||
const maxRunes = 2000
|
||||
if n := len([]rune(prompt)); n > maxRunes {
|
||||
t.Errorf("系统提示词 %d 字,超过上限 %d —— \n"+
|
||||
"新加的内容先问:模型会据此做什么不同的事?答不上来就该进 skill 或工具描述",
|
||||
n, maxRunes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ func TestResident_LifecycleAndNoOrphans(t *testing.T) {
|
||||
t.Fatalf("销毁后 inputch 应回到未分配:%+v", ch)
|
||||
}
|
||||
// 父的入站 inputch(child/<id>)必须在销毁时一并注销,否则登记表残留脏数据——
|
||||
// HomeAgent 实测:destroy 后 child/<id> 仍挂在根 agent 名下,且无工具可单独注销。
|
||||
// HologramAgent 实测:destroy 后 child/<id> 仍挂在根 agent 名下,且无工具可单独注销。
|
||||
if inbound, ok := reg.Lookup("child/child-1"); ok {
|
||||
t.Fatalf("销毁后父的入站 inputch 应被注销,实际残留:%+v", inbound)
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ const defaultChildMaxTurns = 5
|
||||
// 不把父的【输出规则】/【中断消息】/【记忆清理】/【可用技能】/人格那几节一并搬过来:
|
||||
// 子被显式禁用了 output_send__*(结果回父而非直接回用户),也无需保留记忆或响应中断;
|
||||
// 搬来只会白占提示词预算。
|
||||
const childSystemPromptTemplate = `你是 HomeAgent 的子任务助手。
|
||||
const childSystemPromptTemplate = `你是 HologramAgent 的子任务助手。
|
||||
请完成以下任务。完成即可,无需保留记忆或查询历史。
|
||||
|
||||
【执行纪律】这是硬性要求,不是建议:
|
||||
|
||||
@ -1270,7 +1270,7 @@ const (
|
||||
//
|
||||
// 我先写的版本是查库(DominantBlockFingerprint)—— **错的**:
|
||||
// 库里只有旧空间的块时,多数取值就是旧指纹,
|
||||
// 拿它当过滤条件等于「不<EFBFBD><EFBFBD>滤」,旧块原样通过。
|
||||
// 拿它当过滤条件等于「不过滤」,旧块原样通过。
|
||||
// (判据当场抓住:测试库里唯一的块是 old-fp,而当前空间是 fp1。)
|
||||
//
|
||||
// 隔离的语义是「只信任当前空间写入的块」,
|
||||
|
||||
@ -157,7 +157,7 @@ func appendPromptBlock(prompt, block string) string {
|
||||
func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||
prompt := expandPromptVars(a.systemPrompt)
|
||||
if prompt == "" {
|
||||
prompt = "你是小宅,HomeAgent 的看板娘,一个家政型 AI 管家助手。绝不用 Unicode emoji,只用颜文字表达情感,句尾带语气词。WebUI 概览页展示你的立绘。"
|
||||
prompt = "你是小宅,HologramAgent 的看板娘,一个家政型 AI 管家助手。绝不用 Unicode emoji,只用颜文字表达情感,句尾带语气词。WebUI 概览页展示你的立绘。"
|
||||
}
|
||||
|
||||
// 驻留子:在**固定提示词之上**注入任务提示词(设计 §7「创建」)。
|
||||
@ -185,17 +185,20 @@ func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||
// 为什么必须有这一段:此前系统提示词从未解释 L1/L2/L3 是什么,
|
||||
// agent 只能从工具名猜。生产日志实测:agent 几乎从不调用 doc_query,
|
||||
// 即使调用了也只看返回值、不知道原文已被回填到对话时序。
|
||||
prompt = appendPromptBlock(prompt, "【三层记忆】记忆分三层,你要知道自己现在在看哪一层:\n"+
|
||||
"- **对话时序(L1,你现在看得到)**:最近的事件,按时间排列在上面那段【对话时序】里。"+
|
||||
"窗口放不下的会更早被移出,但不会丢。\n"+
|
||||
"- **历史记忆(L2,不直接给你看)**:被移出窗口的事件,连同**完整原文**(含图片/音频)保存在这里。"+
|
||||
"每次你收到新输入,内核会按语义相似度自动挑几篇相关的放在【历史记忆】块里,"+
|
||||
"但那只是片段 —— 要看某次的完整原文与过程,调用 `doc_query`:"+
|
||||
"它会把命中的事件**按原始时间戳插回你的对话时序**(不是返回一段摘要,那些事件真的回到了时间线上)。\n"+
|
||||
"- **图记忆(L3,长期)**:实体与关系(谁是什么、某值现在是多少、谁和谁什么关系),"+
|
||||
"用 `memory_recall` 查、`memory_commit` 记。\n"+
|
||||
"选择口径:要**原文与过程** → `doc_query`;要**关系与当前值** → `memory_recall`;"+
|
||||
"要**成文的可复用知识** → `knowledge_search`。")
|
||||
// 【三层记忆】只留**模型据此选择工具**的语义。
|
||||
//
|
||||
// 技术性内容(L1/L2/L3 编号、向量空间、消费式删除、缩略语)全部下沉到
|
||||
// 对应工具的 description 与 skill:模型只需要知道"哪里有原文、哪里有
|
||||
// 关系、用什么去取",不需要知道内核怎么存。
|
||||
//
|
||||
// ★ 但有一件事必须说:doc_query 会把历史事件**插回时序**,而不是返回
|
||||
// 一段摘要 —— 这与模型对"检索工具"的默认预期相反,不说就会误用。
|
||||
prompt = appendPromptBlock(prompt, "【三层记忆】你这儿有三种记忆,按需要的东西选:\n"+
|
||||
"- **对话时序(L1)**:最近的对话就在上面的【对话时序】里,直接能看。\n"+
|
||||
"- **历史记忆(L2)**:更早的事被移出窗口了,但原文还在。要看某段完整经过用 `doc_query` —— "+
|
||||
"它会把那些事**按原始时间戳插回你的对话时序**,你随后就能像看当前对话一样读它们。\n"+
|
||||
"- **图记忆(L3)**:事实与关系(谁是什么、某值现在多少)用 `memory_recall`;"+
|
||||
"成文的可复用知识用 `knowledge_search`。")
|
||||
|
||||
// 记忆维护:具体选哪个工具、各有什么副作用,都写在 memory_* 的工具描述里
|
||||
//(【记忆清理】前缀)。这里只保留那条**不看描述也会犯**的错:只描述计划不执行。
|
||||
@ -220,11 +223,16 @@ func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||
//
|
||||
// ⚠️ 措辞必须与 batchRunnable 的**真实**判据一致(全批 ParallelSafe
|
||||
// 才并发 + 同通道保序),否则只是把"没说"换成"说错"。
|
||||
prompt = appendPromptBlock(prompt, "【工具执行顺序】同一条回复里给出多个工具调用时,内核**默认并行执行**(同时跑),不是依次执行。这带来三条你必须知道的规则:\n")
|
||||
prompt += "- **不要依赖执行顺序**:若某个调用的参数需要另一个调用的结果,就**分两轮**——先发前一个,看到结果后再发下一个。写在同一轮里就等于假设了不存在的先后。\n"
|
||||
prompt += "- **例外一:同一通道的输出发送会保序**。对**同一个**通道连续调用多次 `output_send__`,内核会**按你发出的顺序依次执行**(用户可见消息顺序敏感),不会并发打乱。所以「先发回执、再发结论」这类有序发送可以放在同一轮;但若后续内容依赖前一条的用户反应,仍应分轮。\n"
|
||||
prompt += "- **例外二:不并发安全的工具不会并发**。写类工具(记忆/知识/文档写入等)与未声明并发安全的工具,**整批会退回依次执行**——同一批里只要有一个这样的工具,这一批就全部串行。这对你是透明的:你不需要判断,只需知道「同轮并发」不是无条件保证的。\n"
|
||||
prompt += "- 工具是否并发安全由**工具自己声明**(`ParallelSafe`),不由你决定;你只需按上面第 1 条判断能否同轮发出。\n"
|
||||
// 【工具执行顺序】只留**模型必须据此作决定**的三条。
|
||||
//
|
||||
// 技术性内容(ParallelSafe 声明、批内降级、内核如何判据)全部下沉:
|
||||
// 那是实现细节,模型既不需要也改不了 —— 讲它只会让回复带上说明书腔。
|
||||
// 判断依据仍是真实的 batchRunnable 语义(勿写反)。
|
||||
prompt = appendPromptBlock(prompt, "【工具执行顺序】同一条回复里的多个工具调用默认**并行**执行。\n"+
|
||||
"- 不要依赖先后:后一个要用前一个的结果时,分两轮发。\n"+
|
||||
"- **例外一**:对同一通道的多次输出会**保序**(按你发出的顺序送达),所以「先回执、再结论」可以放同一轮。\n"+
|
||||
"- **例外二**:写类工具(记忆/知识/文档写入等)以及不声明**并发安全**的工具,这一批会退回依次执行 —— "+
|
||||
"只是变慢,不影响结果,你不用为它调整写法。")
|
||||
|
||||
prompt = appendPromptBlock(prompt, "【输出规则】消息不会自动发送到对话来源通道,你必须自己决定如何回复:\n")
|
||||
prompt += "- **不要假设当前通道是某个固定值**:同一会话里可能同时有多个来源(多设备、多通道、子任务)。\n"
|
||||
@ -838,7 +846,7 @@ func (a *Agent) buildToolDefs() []interface{} {
|
||||
// 而「读本地文件」是 IO —— 三个工具读文件却住在内核进程内,
|
||||
// 无沙箱、无能力面、无审计,正是 capability.go 那套权限梯度要防的事。
|
||||
//
|
||||
// 留在这里的是**待<EFBFBD><EFBFBD>退场**的部分:下面这一大块 pendingMedia 只服务于
|
||||
// 留在这里的是**待退场**的部分:下面这一大块 pendingMedia 只服务于
|
||||
// 那三个工具的工具面。input 通路(processInput 直接把 media block
|
||||
// 拼进本轮请求)**不依赖它**,所以可安全移除。
|
||||
// 退场顺序见 docs 或本次提交的说明;先确保插件侧能力等价。
|
||||
|
||||
@ -696,7 +696,7 @@ func (m *IOManager) GetInputChannelDef(name string) (ChannelDef, bool) {
|
||||
}
|
||||
|
||||
// ToolDefOf 按工具名取其声明(含 Parameters schema)。
|
||||
// 用途:内核在执行前按 schema 预校验——没有它就只<EFBFBD><EFBFBD><EFBFBD>校验到插件工具,
|
||||
// 用途:内核在执行前按 schema 预校验——没有它就只能校验到插件工具,
|
||||
// 而设备/通道工具(cmd_run、files_write 等)会完全绕过校验。
|
||||
func (m *IOManager) ToolDefOf(name string) (ToolDef, bool) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
// PersonaStaleHints 必须能认出会腐坏的人格内容——版本号字面量与已删除机制。
|
||||
func TestPersonaStaleHints(t *testing.T) {
|
||||
// 现场真实文本(线上人格卡的原文)
|
||||
stale := "你是 HomeAgent(内核代号 HΔ-Kernel,当前版本 v0.9.0)。\n当前运行的二进制是 v0.9.0(C ABI v2,构建于 2026-08-15)。"
|
||||
stale := "你是 HologramAgent(内核代号 HΔ-Kernel,当前版本 v0.9.0)。\n当前运行的二进制是 v0.9.0(C ABI v2,构建于 2026-08-15)。"
|
||||
hints := PersonaStaleHints(stale)
|
||||
if len(hints) == 0 {
|
||||
t.Fatal("未识别出写死版本号与 C ABI 的人格文本")
|
||||
@ -32,7 +32,7 @@ func TestPersonaStaleHints(t *testing.T) {
|
||||
// DefaultPersonaProbeClean 由 config 包的默认模板等价物构成——
|
||||
// 这里不复用 config 包以避免 import cycle,只断言「不含版本号与旧机制」的文本不被误报。
|
||||
func DefaultPersonaProbeClean() string {
|
||||
return "你是 HomeAgent(内核代号 HΔ-Kernel)。外部插件是独立子进程,经 stdio JSON-RPC 通信;" +
|
||||
return "你是 HologramAgent(内核代号 HΔ-Kernel)。外部插件是独立子进程,经 stdio JSON-RPC 通信;" +
|
||||
"被问到版本时以运行时快照为准。"
|
||||
}
|
||||
|
||||
|
||||
@ -638,7 +638,7 @@ var defaultSources = map[string]map[string]string{
|
||||
// (现场:人格卡写死 v0.9.0 与早已删除的 C ABI,实例被问版本时自述错误)。
|
||||
// 被问到版本/构建信息时,要求 agent 读运行时快照。
|
||||
// - 不得把已删除的机制当作现行机制描述。
|
||||
const DefaultPersonaPrompt = `你是 HomeAgent(内核代号 HΔ-Kernel)——一个持续运行的个人管家。
|
||||
const DefaultPersonaPrompt = `你是 HologramAgent(内核代号 HΔ-Kernel)——一个持续运行的个人管家。
|
||||
可靠、话不多,对自己的三层记忆(Context → Document → Graph)引以为傲。
|
||||
被问版本或构建信息时以运行时快照(healthcheck_kernel)为准,不要凭记忆断言。
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ func (Module) Init(k *kernelboot.KernelState) error {
|
||||
|
||||
func init() { kernelmod.Register(Module{}) }
|
||||
|
||||
const defaultSystemPrompt = `你是 HomeAgent,一个持续运行的个人管家。
|
||||
const defaultSystemPrompt = `你是 HologramAgent,一个持续运行的个人管家。
|
||||
|
||||
可用工具与已安装技能由系统在每轮自动注入:
|
||||
- 工具的 description 写了「何时该用、参数含义、有什么坑」,调用前先读它,不要凭工具名猜用法;
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
// ===== 从目录导入知识 =====
|
||||
//
|
||||
// 能力缺口:导入只能一条条 Add(knowledge_create),agent 拿到一份
|
||||
// 200 页的文档目录就得调 200 次工具,且每次都要自己决定分类与名<EFBFBD><EFBFBD>。
|
||||
// 200 页的文档目录就得调 200 次工具,且每次都要自己决定分类与名字。
|
||||
//
|
||||
// 语义是**复制**不是引用:源文件删掉后知识库里的副本仍完整可用。
|
||||
// 文本落 <知识根>/<分类>/<名>/content.md(Write 的既有行为);
|
||||
|
||||
@ -15,7 +15,7 @@ func TestAbstain_符号零出现则拒答(t *testing.T) {
|
||||
blocks := []MemoryBlock{
|
||||
{ID: "1", Text: "脚本路径改为 /data/homeagent"},
|
||||
{ID: "2", Text: "13010/13011 而非 12011"},
|
||||
{ID: "3", Text: "从零开发 HomeAgent QQ 插件:使用 plugindev 工具链"},
|
||||
{ID: "3", Text: "从零开发 HologramAgent QQ 插件:使用 plugindev 工具链"},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
|
||||
@ -152,7 +152,7 @@ func (c DriftCandidate) String() string {
|
||||
//
|
||||
// 判据(三条同时成立才列为候选):
|
||||
// 1. 值形态相同(classifyValue 相同)
|
||||
// 2. 共<EFBFBD><EFBFBD><EFBFBD>至少一个 subject(同实体上的属性)
|
||||
// 2. 共同至少一个 subject(同实体上的属性)
|
||||
// 3. 字段名不同(否则不是漂移)
|
||||
func containsDim(ss []string, s string) bool {
|
||||
for _, x := range ss {
|
||||
|
||||
@ -110,7 +110,7 @@ func TestFuse_向量失手时符号能救回(t *testing.T) {
|
||||
|
||||
// ★ 反向:符号不能压过明显更相关的向量命中。
|
||||
func TestFuse_不误伤强向量命中(t *testing.T) {
|
||||
strong := MemoryBlock{ID: "b-strong", Text: "从零开发 HomeAgent QQ 插件:使用 plugindev 工具链"}
|
||||
strong := MemoryBlock{ID: "b-strong", Text: "从零开发 HologramAgent QQ 插件:使用 plugindev 工具链"}
|
||||
other := MemoryBlock{ID: "b-other", Text: "AgentMail 投递规则"}
|
||||
|
||||
vecHits := []BlockHit{
|
||||
|
||||
@ -307,7 +307,7 @@ func TestSceneAdapt_悬空清理认edge(t *testing.T) {
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 停旧表双写 —— 判据(2026-10-04)
|
||||
//
|
||||
// ★ 目标不是「旧表被删」,而是「旧表停止增长、冻结为历史」<EFBFBD><EFBFBD><EFBFBD>
|
||||
// ★ 目标不是「旧表被删」,而是「旧表停止增长、冻结为历史」。
|
||||
// 删表是不可逆的,且读方虽已全切块,仍需要一段时间观察。
|
||||
//
|
||||
// ★ 判据的核心是**增长量**,不是「有没有 INSERT 语句」——
|
||||
|
||||
@ -390,7 +390,7 @@ func TestLegacyEntityBlockID_稳定可读(t *testing.T) {
|
||||
//
|
||||
// 背景:实测(真实 chineseclip + 真库)同一属性先后给两个值时,
|
||||
// 「值班室分机号 4324」与「值班室分机号 4379」短句向量相似度是
|
||||
// 0.9284 vs 0.9298 —— 数值上区分不了。若迁移把块的时<EFBFBD><EFBFBD><EFBFBD>全写成 NOW(),
|
||||
// 0.9284 vs 0.9298 —— 数值上区分不了。若迁移把块的时间全写成 NOW(),
|
||||
// overwrite 检索就彻底没救了。
|
||||
//
|
||||
// ★ 断言覆盖**全部**块:初版只查了含关键字的那一个,结果把
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
// Package meta 收集 HomeAgent 内核的全部元数据。
|
||||
// Package meta 收集 HologramAgent 内核的全部元数据。
|
||||
// 版本号通过 `go build -ldflags` 注入,默认值为 dev 版本。
|
||||
// SDK 仓的 meta/meta.go 应与此保持同步。
|
||||
package meta
|
||||
|
||||
var (
|
||||
// Version 是 HomeAgent 内核版本号。
|
||||
// Version 是 HologramAgent 内核版本号。
|
||||
// 通过 `-ldflags="-X github.com/JianFeeeee/HologramAgent/internal/meta.Version=vX.Y.Z"` 注入。
|
||||
//
|
||||
// 1.0.0:外部插件从 C ABI 动态库迁到子进程 + 共享内存。
|
||||
@ -39,7 +39,7 @@ var (
|
||||
BuildTime = "unknown"
|
||||
|
||||
// KernelName 是内核名称。
|
||||
KernelName = "HomeAgent"
|
||||
KernelName = "HologramAgent"
|
||||
|
||||
// SDKCompatibleVersion 是此内核可兼容的最高 SDK 版本(semver)。
|
||||
//
|
||||
|
||||
@ -94,9 +94,9 @@ type PluginManifest struct {
|
||||
// 那里能接受(0 与"未声明"语义相同),这里**不能**。
|
||||
AutoStart *bool `json:"auto_start,omitempty"`
|
||||
|
||||
// Proxies 声明本插件需要 HomeAgent 反代出去的服务(自带 Web UI / HTTP API)。
|
||||
// Proxies 声明本插件需要 HologramAgent 反代出去的服务(自带 Web UI / HTTP API)。
|
||||
//
|
||||
// 契约定义在公开 SDK(sdk.ProxyDecl),这里只做载体:HomeAgent 加载插件时
|
||||
// 契约定义在公开 SDK(sdk.ProxyDecl),这里只做载体:HologramAgent 加载插件时
|
||||
// 读取并聚合,按 Host 子域路由从 webui 的同一端口转发出去。**不声明 = 不被反代**。
|
||||
//
|
||||
// 字段解析忽略未知键(本仓无 DisallowUnknownFields),因此加这个字段
|
||||
|
||||
@ -69,7 +69,7 @@ func TestManifestWithoutTimeoutKeepsLegacyBehavior(t *testing.T) {
|
||||
// 用真实的存量 plugin.json 内容(qq 插件,无 handshake_timeout_sec)
|
||||
dir := t.TempDir()
|
||||
legacy := `{
|
||||
"author": "HomeAgent",
|
||||
"author": "HologramAgent",
|
||||
"description": "QQ 消息收发插件,通过 NapCat 协议桥接",
|
||||
"entry": "plugin.bin",
|
||||
"name": "qq",
|
||||
|
||||
@ -17,7 +17,7 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// shmMagic 标识 HomeAgent 的 StageContext 共享段。
|
||||
// shmMagic 标识 HologramAgent 的 StageContext 共享段。
|
||||
//
|
||||
// **不设版本号**:段布局自描述 —— 内核把描述符个数写进 header
|
||||
// (offFieldCount),插件据此定位标志位,不再用自己编译期的常量推算。
|
||||
@ -27,7 +27,7 @@ import (
|
||||
// 协议兼容性由双方版本号的**主版本**在握手上校验(见 protocol.go 的
|
||||
// MajorVersion)。
|
||||
const (
|
||||
shmMagic uint32 = 0x48415348 // "HASH" — HomeAgent SHared
|
||||
shmMagic uint32 = 0x48415348 // "HASH" — HologramAgent SHared
|
||||
)
|
||||
|
||||
// 段布局(所有偏移均相对**段起始**,arena 内偏移相对 arenaBase):
|
||||
|
||||
@ -31,9 +31,9 @@ import (
|
||||
var shmNameSeq atomic.Uint64
|
||||
|
||||
const (
|
||||
shmNamePrefix = "Local\\HomeAgentShm"
|
||||
evtRingNamePfx = "Local\\HomeAgentEvtRing"
|
||||
evtEventNamePfx = "Local\\HomeAgentEvtSignal"
|
||||
shmNamePrefix = "Local\\HologramAgentShm"
|
||||
evtRingNamePfx = "Local\\HologramAgentEvtRing"
|
||||
evtEventNamePfx = "Local\\HologramAgentEvtSignal"
|
||||
envStageShmName = "HOMEAGENT_SHM_STAGE"
|
||||
envEvtRingName = "HOMEAGENT_SHM_EVTRING"
|
||||
envEvtEventName = "HOMEAGENT_EVT_EVENT"
|
||||
|
||||
@ -268,7 +268,7 @@ func (p *Plugin) downloadImage(imgURL, baseName string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; HomeAgent/1.0)")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; HologramAgent/1.0)")
|
||||
resp, err := dl.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@ -596,14 +596,14 @@ async function runCLI(skillsDir, cliArgs) {
|
||||
}
|
||||
|
||||
case 'auth:status': {
|
||||
console.log('Auth status: not implemented (running in HomeAgent mode)');
|
||||
console.log('Auth status: not implemented (running in HologramAgent mode)');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'config:get': {
|
||||
const key = cliArgs[1];
|
||||
if (!key) throw new Error('Usage: openclaw config:get <key>');
|
||||
// TODO: read from HomeAgent config system when bridged
|
||||
// TODO: read from HologramAgent config system when bridged
|
||||
console.log(`(not available in CLI mode: ${key})`);
|
||||
break;
|
||||
}
|
||||
@ -612,7 +612,7 @@ async function runCLI(skillsDir, cliArgs) {
|
||||
const key = cliArgs[1];
|
||||
const value = cliArgs[2];
|
||||
if (!key || value === undefined) throw new Error('Usage: openclaw config:set <key> <value>');
|
||||
// TODO: write to HomeAgent config system when bridged
|
||||
// TODO: write to HologramAgent config system when bridged
|
||||
console.log(`(not available in CLI mode: ${key}=${value})`);
|
||||
break;
|
||||
}
|
||||
@ -635,7 +635,7 @@ async function runCLI(skillsDir, cliArgs) {
|
||||
|
||||
case '--version':
|
||||
case 'version':
|
||||
console.log('HomeAgent OpenClaw Adapter 1.0.0 (openclaw-compatible)');
|
||||
console.log('HologramAgent OpenClaw Adapter 1.0.0 (openclaw-compatible)');
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
|
||||
@ -155,9 +155,9 @@ func (r *ProviderRegistry) Dispatch(typeStr string, data json.RawMessage, plugin
|
||||
}
|
||||
switch lookupType {
|
||||
case "provider":
|
||||
log.Printf("[clawhubadapter] generic LLM provider from %s handled natively by HomeAgent", pluginName)
|
||||
log.Printf("[clawhubadapter] generic LLM provider from %s handled natively by HologramAgent", pluginName)
|
||||
case "embedding", "memory_embedding":
|
||||
log.Printf("[clawhubadapter] embedding provider %s from %s ignored (HomeAgent uses native embeddings)", lookupType, pluginName)
|
||||
log.Printf("[clawhubadapter] embedding provider %s from %s ignored (HologramAgent uses native embeddings)", lookupType, pluginName)
|
||||
default:
|
||||
log.Printf("[clawhubadapter] unknown provider type: %s (plugin: %s)", typeStr, pluginName)
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ function requireSkillsDir() {
|
||||
const cmd = process.argv[2];
|
||||
|
||||
if (!cmd || cmd === 'help' || cmd === '--help') {
|
||||
console.log(`HomeAgent OpenClaw Adapter 1.0.0 (openclaw-compatible)
|
||||
console.log(`HologramAgent OpenClaw Adapter 1.0.0 (openclaw-compatible)
|
||||
Usage: openclaw <command> [args]
|
||||
|
||||
Commands:
|
||||
@ -89,7 +89,7 @@ Commands:
|
||||
}
|
||||
|
||||
if (cmd === '--version' || cmd === 'version') {
|
||||
console.log('HomeAgent OpenClaw Adapter 1.0.0 (openclaw-compatible)');
|
||||
console.log('HologramAgent OpenClaw Adapter 1.0.0 (openclaw-compatible)');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@ -123,8 +123,8 @@ if (cmd === 'doctor') {
|
||||
}
|
||||
|
||||
if (cmd === 'update') {
|
||||
console.log('Update check: HomeAgent OpenClaw Adapter is bundled with HomeAgent.');
|
||||
console.log('To update, update HomeAgent itself.');
|
||||
console.log('Update check: HologramAgent OpenClaw Adapter is bundled with HologramAgent.');
|
||||
console.log('To update, update HologramAgent itself.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ if (cmd === 'auth:login' || cmd === 'auth:qrcode') {
|
||||
}
|
||||
|
||||
if (cmd === 'auth:status') {
|
||||
console.log('Auth status: running in HomeAgent mode (not applicable)');
|
||||
console.log('Auth status: running in HologramAgent mode (not applicable)');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
@ -527,7 +527,7 @@ func (p *Plugin) cmdStatus(conn net.Conn, s *sdk.PluginSDK) {
|
||||
|
||||
writeLine(conn, map[string]interface{}{
|
||||
"type": "response",
|
||||
"content": fmt.Sprintf(`HomeAgent 内核状态
|
||||
"content": fmt.Sprintf(`HologramAgent 内核状态
|
||||
状态: running
|
||||
运行: %s
|
||||
LLM: %s
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Package kbtree 把知识库的**分类树**作为独立 HTTP 服务暴露给外部 agent。
|
||||
//
|
||||
// 定位(与 HomeAgent 内部知识工具的分工):
|
||||
// - HomeAgent 自己的 agent **直接调内部方法**(knowledge_search /
|
||||
// 定位(与 HologramAgent 内部知识工具的分工):
|
||||
// - HologramAgent 自己的 agent **直接调内部方法**(knowledge_search /
|
||||
// knowledge_create 等内核工具),走的是进程内直调,最快、也最少攻击面。
|
||||
// - 其它 agent(别的进程、别的机器、别的语言写的)走本插件的 HTTP 接口。
|
||||
//
|
||||
@ -194,7 +194,7 @@ func (p *Plugin) handleRoot(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"service": "kbtree",
|
||||
"purpose": "HomeAgent 知识库分类树的只读访问(供外部 agent 使用)",
|
||||
"purpose": "HologramAgent 知识库分类树的只读访问(供外部 agent 使用)",
|
||||
"read_only": true,
|
||||
"endpoints": []string{
|
||||
"GET /tree?category=&depth=&items= 分类树(可指定子树/层数)",
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
|
||||
// scope 是知识库对外暴露范围。
|
||||
//
|
||||
// 存在的理由:kbtree 是**唯一**把知识库开放给外部进程的通道(HomeAgent
|
||||
// 存在的理由:kbtree 是**唯一**把知识库开放给外部进程的通道(HologramAgent
|
||||
// 自己的 agent 走进程内直调,不经此)。没有范围限制时,任何拿到 token 的
|
||||
// agent 都能 /tree 列出全部条目、/search 取回任意条目全文 —— 而本机库里
|
||||
// 混着个人内容(教材摘录、课表、身份合并规则),不该 broadly 可读。
|
||||
|
||||
@ -422,7 +422,7 @@ func (p *Plugin) handleScreensue(args map[string]interface{}) (interface{}, erro
|
||||
return p.screensueLinux(content, duration)
|
||||
case "darwin":
|
||||
// macOS 用 osascript 弹窗
|
||||
psScript := fmt.Sprintf(`display dialog "%s" with title "HomeAgent" buttons {"OK"} default button "OK giving up after %d"`, escapeAppleScriptString(content), duration)
|
||||
psScript := fmt.Sprintf(`display dialog "%s" with title "HologramAgent" buttons {"OK"} default button "OK giving up after %d"`, escapeAppleScriptString(content), duration)
|
||||
cmd := exec.Command("osascript", "-e", psScript)
|
||||
_ = cmd.Run()
|
||||
return map[string]interface{}{"content": "屏幕显示已触发"}, nil
|
||||
@ -431,7 +431,7 @@ func (p *Plugin) handleScreensue(args map[string]interface{}) (interface{}, erro
|
||||
psScript := fmt.Sprintf(`
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.Text = "HomeAgent"
|
||||
$form.Text = "HologramAgent"
|
||||
$form.Size = New-Object Drawing.Size(500,300)
|
||||
$form.StartPosition = "CenterScreen"
|
||||
$form.TopMost = $true
|
||||
@ -461,14 +461,14 @@ func (p *Plugin) screensueLinux(content string, duration int) (interface{}, erro
|
||||
tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("local_screensue_%d.txt", time.Now().UnixNano()))
|
||||
os.WriteFile(tmpFile, []byte(content), 0644)
|
||||
defer os.Remove(tmpFile)
|
||||
cmd := exec.Command("xterm", "-T", "HomeAgent", "-e", "cat", tmpFile)
|
||||
cmd := exec.Command("xterm", "-T", "HologramAgent", "-e", "cat", tmpFile)
|
||||
_ = cmd.Start()
|
||||
go func() { time.Sleep(time.Duration(duration) * time.Second); cmd.Process.Kill() }()
|
||||
return map[string]interface{}{"content": "屏幕显示已触发(xterm)"}, nil
|
||||
}
|
||||
// 回退 notify-send
|
||||
if _, err := exec.LookPath("notify-send"); err == nil {
|
||||
_ = exec.Command("notify-send", "HomeAgent", content).Run()
|
||||
_ = exec.Command("notify-send", "HologramAgent", content).Run()
|
||||
return map[string]interface{}{"content": "通知已发送"}, nil
|
||||
}
|
||||
return map[string]interface{}{"isError": true, "content": "无可用显示方式(安装 xterm 或 libnotify-bin)"}, nil
|
||||
|
||||
@ -199,7 +199,7 @@ func (p *Plugin) Stop() error {
|
||||
func (p *Plugin) registerTools(s *sdk.PluginSDK) {
|
||||
s.RegisterTool("plugin_install", sdk.ToolDef{
|
||||
Name: "plugin_install",
|
||||
Description: "安装 HomeAgent 插件包(.hmap)。两种来源:url(http/https 下载)或 path(本机路径,配合 plugindev_build 的产物用这个)。插件已存在时传 overwrite=true 原地更新(升级/降级/重装,保留配置表,无需卸载重装)。更新后需调用 plgreload 或重启生效。",
|
||||
Description: "安装 HologramAgent 插件包(.hmap)。两种来源:url(http/https 下载)或 path(本机路径,配合 plugindev_build 的产物用这个)。插件已存在时传 overwrite=true 原地更新(升级/降级/重装,保留配置表,无需卸载重装)。更新后需调用 plgreload 或重启生效。",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
|
||||
@ -300,7 +300,7 @@ func TestWSBinaryMediaToFile(t *testing.T) {
|
||||
}
|
||||
// ★ 2026-10-06:文件名含内容摘要(SaveInlineMedia)。
|
||||
// 截屏/录像这类会被反复抓取的媒体,用 reqID 命名会把同内容存成多份、
|
||||
// 不同内容却可能覆盖 —— 摘要后缀两<EFBFBD><EFBFBD><EFBFBD>都避免。
|
||||
// 不同内容却可能覆盖 —— 摘要后缀两边都避免。
|
||||
if filepath.Dir(fp) != mediaDir {
|
||||
t.Fatalf("file 应落在媒体目录 %s,实际 %s", mediaDir, fp)
|
||||
}
|
||||
@ -457,7 +457,7 @@ func TestScreenseeEndToEnd(t *testing.T) {
|
||||
}()
|
||||
|
||||
// agent 调用 screensee
|
||||
// ★ 2026-10-06:默认走落盘 + 回路径;旧的自<EFBFBD><EFBFBD><EFBFBD>描述路径用 auto_describe=true。
|
||||
// ★ 2026-10-06:默认走落盘 + 回路径;旧的自动描述路径用 auto_describe=true。
|
||||
res, err := dev.Execute("screensee", map[string]interface{}{
|
||||
"device_id": "see-dev", "auto_describe": true,
|
||||
})
|
||||
|
||||
@ -43,7 +43,7 @@ func (d *devicectlDevice) Tools() []agentIO.ToolDef {
|
||||
{
|
||||
Name: "devicedetect",
|
||||
Description: "扫描并列出已接入设备网关的设备(含在线/离线状态与授权状态)。" +
|
||||
"用途:查看当前有哪些设备连接了 HomeAgent、是否在线、是否已授权。" +
|
||||
"用途:查看当前有哪些设备连接了 HologramAgent、是否在线、是否已授权。" +
|
||||
"参数 kind 可选,只返回该种类的设备。返回值 device_id 用于后续 device_ctl_* 工具。",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
|
||||
@ -77,7 +77,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
|
||||
// ---- 设置 ----------------
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "listen_addr", Default: defaultAddr, Type: "string", DisplayName: "监听地址", Description: "设备网关 HTTP/WS 监听地址(默认 127.0.0.1:9890,仅本机);填 127.0.0.1:0 让系统分配空闲端口", Category: "remotedevice"})
|
||||
// ---- 反代声明(HomeAgent 自带能力)----
|
||||
// ---- 反代声明(HologramAgent 自带能力)----
|
||||
//
|
||||
// 设备网关需要被外部访问(设备/客户端要连 WS),但它默认只监听本机
|
||||
// 127.0.0.1:9890。声明后由 webui 的对外端口按 Host 子域反代出去,
|
||||
|
||||
@ -65,7 +65,7 @@ func TestSeqCreateSchemaDeclaresRequired(t *testing.T) {
|
||||
|
||||
// ③ seq_create 的 description 必须说明 groups 与 file **二选一**。
|
||||
//
|
||||
// 这是最容易让模型犯错的地方:两个都传或都不传该怎么<EFBFBD><EFBFBD><EFBFBD>,必须写清。
|
||||
// 这是最容易让模型犯错的地方:两个都传或都不传该怎么办,必须写清。
|
||||
func TestSeqCreateExplainsMutuallyExclusiveInput(t *testing.T) {
|
||||
p := newTestPlugin(t)
|
||||
desc := p.toolDefs()["seq_create"].Description
|
||||
|
||||
@ -349,7 +349,7 @@ func TestServerHasReadSideTimeouts(t *testing.T) {
|
||||
if srv.IdleTimeout <= 0 {
|
||||
t.Errorf("IdleTimeout = %v,必须 > 0(keep-alive 空闲连接会无限累积)", srv.IdleTimeout)
|
||||
}
|
||||
// ReadTimeout 限制「读完整请求」的<EFBFBD><EFBFBD>间(含 body),防慢速上传。
|
||||
// ReadTimeout 限制「读完整请求」的时间(含 body),防慢速上传。
|
||||
if srv.ReadTimeout <= 0 {
|
||||
t.Errorf("ReadTimeout = %v,必须 > 0(慢速上传会长期占用连接)", srv.ReadTimeout)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>HomeAgent Dashboard</title>
|
||||
<title>HologramAgent Dashboard</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<script
|
||||
src="/static/three.min.js"
|
||||
@ -40,7 +40,7 @@
|
||||
<h1>
|
||||
<img
|
||||
src="/logo.svg"
|
||||
alt="HomeAgent"
|
||||
alt="HologramAgent"
|
||||
style="
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
@ -49,7 +49,7 @@
|
||||
flex-shrink: 0;
|
||||
border-radius: 6px;
|
||||
"
|
||||
/>HomeAgent
|
||||
/>HologramAgent
|
||||
</h1>
|
||||
<div class="nav-links">
|
||||
<!--
|
||||
@ -201,7 +201,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<div class="breadcrumb" id="breadcrumb">
|
||||
<span class="crumb-home">HomeAgent</span>
|
||||
<span class="crumb-home">HologramAgent</span>
|
||||
<span class="crumb-sep">/</span>
|
||||
<span class="crumb-current" data-i18n="navOverview">概览</span>
|
||||
</div>
|
||||
|
||||
@ -2009,7 +2009,7 @@ function updateOverview() {
|
||||
ovSet(
|
||||
"ov-version-sub",
|
||||
kv
|
||||
? (kb.kernel_name || "HomeAgent") +
|
||||
? (kb.kernel_name || "HologramAgent") +
|
||||
(kb.commit && kb.commit !== "unknown"
|
||||
? " · " + String(kb.commit).slice(0, 7)
|
||||
: "")
|
||||
@ -5590,7 +5590,7 @@ function renderProxyServicesCard() {
|
||||
html +=
|
||||
'<div style="font-size:12px;color:var(--text-muted);margin-bottom:10px">' +
|
||||
__(
|
||||
"插件服务经 HomeAgent 同一端口反代。子域形态需要 DNS 能解析 ",
|
||||
"插件服务经 HologramAgent 同一端口反代。子域形态需要 DNS 能解析 ",
|
||||
"Proxied through the same port. The subdomain form needs DNS for ",
|
||||
) +
|
||||
"<b>" +
|
||||
@ -6281,7 +6281,7 @@ function renderKernel() {
|
||||
'<div class="kv-row"><span class="key">' +
|
||||
__("内核", "Kernel") +
|
||||
'</span><span class="val">' +
|
||||
escHtml(b.kernel_name || "HomeAgent") +
|
||||
escHtml(b.kernel_name || "HologramAgent") +
|
||||
"</span></div>" +
|
||||
'<div class="kv-row"><span class="key">' +
|
||||
__("内核版本", "Kernel version") +
|
||||
|
||||
@ -36,7 +36,7 @@ var dashboardFS embed.FS
|
||||
// 看到「3D 星图不可用(CDN 加载失败)」—— 服务端 curl 同一 URL 是 200,
|
||||
// 说明是**浏览器**访问不到公网 CDN(内网 / 出口受限 / 断网)。
|
||||
//
|
||||
// 换 CDN 只是把同一个赌注重下<EFBFBD><EFBFBD><EFBFBD>遍:HomeAgent 明确支持离线与内网部署,
|
||||
// 换 CDN 只是把同一个赌注重下一遍:HologramAgent 明确支持离线与内网部署,
|
||||
// 前端却有 4 个硬依赖在公网上,断网即坏且用户无从修复。
|
||||
//
|
||||
// 连带的两个理由:
|
||||
@ -50,7 +50,7 @@ var staticFS embed.FS
|
||||
|
||||
var dashboardHTML string
|
||||
|
||||
const loginHTML = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>HomeAgent 登录</title><style>
|
||||
const loginHTML = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>HologramAgent 登录</title><style>
|
||||
:root{--sakura-300:#ffb3c8;--sakura-400:#ff7fac;--sakura-500:#f33b7c;--frost-300:#88c0d0;--text-primary:#e8e6ee;--text-secondary:#a0a3b5;--text-muted:#6e7284;--bg-primary:#0d0d16;--bg-card:rgba(24,24,38,0.72);--bg-input:rgba(13,13,22,0.6);--border-color:rgba(255,255,255,0.09);--glass-border:rgba(255,255,255,0.12);--glass-blur:20px;--radius-lg:18px;--radius-md:12px;--radius-pill:999px;--shadow-glow:0 0 18px rgba(243,59,124,0.35);--ease-out:cubic-bezier(.22,.61,.36,1)}
|
||||
[data-theme="light"]{--text-primary:#23252e;--text-secondary:#5b5f73;--text-muted:#9aa0b5;--bg-primary:#f6f3f8;--bg-card:rgba(255,255,255,0.72);--bg-input:rgba(255,255,255,0.8);--border-color:rgba(35,37,46,0.1);--glass-border:rgba(255,255,255,0.75);--shadow-glow:0 0 18px rgba(243,59,124,0.28)}
|
||||
*{box-sizing:border-box}
|
||||
@ -74,7 +74,7 @@ button:disabled{opacity:.6;cursor:not-allowed;transform:none}
|
||||
.foot{margin-top:20px;font-size:11px;color:var(--text-muted);text-align:center}
|
||||
.foot svg{width:12px;height:12px;vertical-align:-2px}
|
||||
@media(max-width:480px){.login-card{padding:28px 22px 22px}}
|
||||
</style></head><body><div class="login-wrap"><div class="login-card"><div class="logo"><img src="/logo.svg" alt="HomeAgent"></div><h1>HomeAgent</h1><p class="sub">智能家居助手控制台</p><form id="login-form"><label>用户名</label><input id="username" autocomplete="username" placeholder="请输入用户名" required><label>密码</label><input id="password" type="password" autocomplete="current-password" placeholder="请输入密码" required><button type="submit" id="submit-btn">登 录</button><div id="err" class="err"></div></form></div><div class="foot">HomeAgent · NapCat Theme</div></div><script>
|
||||
</style></head><body><div class="login-wrap"><div class="login-card"><div class="logo"><img src="/logo.svg" alt="HologramAgent"></div><h1>HologramAgent</h1><p class="sub">智能家居助手控制台</p><form id="login-form"><label>用户名</label><input id="username" autocomplete="username" placeholder="请输入用户名" required><label>密码</label><input id="password" type="password" autocomplete="current-password" placeholder="请输入密码" required><button type="submit" id="submit-btn">登 录</button><div id="err" class="err"></div></form></div><div class="foot">HologramAgent · NapCat Theme</div></div><script>
|
||||
(function(){var m=window.matchMedia('(prefers-color-scheme: light)');function apply(){document.documentElement.setAttribute('data-theme',m.matches?'light':'dark')}apply();m.addEventListener('change',apply)})();
|
||||
document.getElementById('login-form').addEventListener('submit',async(e)=>{e.preventDefault();const username=document.getElementById('username').value.trim();const password=document.getElementById('password').value;const err=document.getElementById('err');const btn=document.getElementById('submit-btn');err.textContent='';if(!username||!password){err.textContent='请输入用户名和密码';return}btn.disabled=true;btn.textContent='登录中...';try{const r=await fetch('/api/v1/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username,password})});if(r.ok){location.href='/';return}let data={};try{data=await r.json()}catch(_){}err.textContent=data.error||'登录失败'}catch(_){err.textContent='网络错误,请重试'}finally{btn.disabled=false;btn.textContent='登 录'}});
|
||||
document.getElementById('password').addEventListener('keydown',function(e){if(e.key==='Enter')document.getElementById('login-form').dispatchEvent(new Event('submit'))});
|
||||
|
||||
@ -312,7 +312,7 @@ type openAIMessage struct {
|
||||
// OpenAI 客户端把它当作能力探测(probe)。请求方要的不是模型清单本身,
|
||||
// 而是一个「这个端点讲 OpenAI 协议」的确认。
|
||||
//
|
||||
// 所以这里返回 HomeAgent 自身作为唯一条目:调用方无论填哪个 id 都会
|
||||
// 所以这里返回 HologramAgent 自身作为唯一条目:调用方无论填哪个 id 都会
|
||||
// 被路由到同一个 agent(本端点的 model 参数确实只是透传)。
|
||||
// 不谎报 GPT 之类的名字 —— 那会让用户以为能选模型,而实际不能。
|
||||
func (h *Handler) handleOpenAIModels(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -217,7 +217,7 @@ func TestClientsRecognizeServerEventTypes(t *testing.T) {
|
||||
mustKnow []string
|
||||
}{
|
||||
{"GUI", "../../../cmd/gui/renderer/app.js", false, sseMustKnow},
|
||||
{"鸿蒙", "../../../cmd/ohos/HomeAgent/entry/src/main/ets", true, sseMustKnow},
|
||||
{"鸿蒙", "../../../cmd/ohos/HologramAgent/entry/src/main/ets", true, sseMustKnow},
|
||||
{"CLI", "../../../cmd/waiter", true, cliMustKnow},
|
||||
}
|
||||
|
||||
@ -280,7 +280,7 @@ func TestNoPhantomFrameTypes(t *testing.T) {
|
||||
|
||||
// 客户端源码里搜幽灵字面量(排除注释行,避免判据被说明文字喂饱)。
|
||||
scan := []string{
|
||||
"../../../cmd/ohos/HomeAgent/entry/src/main/ets/common/ChatSse.ets",
|
||||
"../../../cmd/ohos/HologramAgent/entry/src/main/ets/common/ChatSse.ets",
|
||||
}
|
||||
|
||||
for _, p := range scan {
|
||||
@ -413,7 +413,7 @@ func TestDeviceBridgeOpCoverage(t *testing.T) {
|
||||
downOps := []string{"bind_ack", "cmd", "hello_ack", "push", "cmd_speech_start", "cmd_speech_end"}
|
||||
|
||||
// 客户端源码:鸿蒙端设备桥。
|
||||
const p = "../../../cmd/ohos/HomeAgent/entry/src/main/ets/common/DeviceBridge.ets"
|
||||
const p = "../../../cmd/ohos/HologramAgent/entry/src/main/ets/common/DeviceBridge.ets"
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatalf("读 %s: %v", p, err)
|
||||
|
||||
@ -406,7 +406,7 @@ func TestHandleStaticServesHTML(t *testing.T) {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), "HomeAgent Dashboard") {
|
||||
if !strings.Contains(w.Body.String(), "HologramAgent Dashboard") {
|
||||
t.Error("expected dashboard HTML")
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ func TestOpenAIModelsRequiresAuth(t *testing.T) {
|
||||
// 假流式实现 → 先同步等 InjectTextSync 返回(被扣住,阻塞数秒),
|
||||
// 等不到就什么都发不出去 → 首帧迟到数秒。
|
||||
//
|
||||
// 所以断言「首帧远早于总计 700ms 的生成时间」<EFBFBD><EFBFBD>就是判据的全部。
|
||||
// 所以断言「首帧远早于总计 700ms 的生成时间」就是判据的全部。
|
||||
func TestOpenAIStreamIsActuallyStreaming(t *testing.T) {
|
||||
srv, iom, bus := newOpenAITestServer(t)
|
||||
|
||||
|
||||
@ -240,7 +240,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "username", Default: "admin", Type: "string", DisplayName: "登录用户名", Description: "Web 控制台登录用户名", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "password", Default: "", Type: "password", DisplayName: "Web 控制台登录密码", Description: "Web 控制台登录密码", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "session_ttl_hours", Default: "24", Type: "int", DisplayName: "会话时长(小时)", Description: "登录 cookie 有效时长", Category: "webui"})
|
||||
// ---- 通用反代(HomeAgent 自带能力)----
|
||||
// ---- 通用反代(HologramAgent 自带能力)----
|
||||
// 基域名:默认 localhost ⇒ <标签>.localhost:<端口> 开箱即用(RFC 6761 强制
|
||||
// 解析到 loopback,无需 DNS/证书/hosts)。远程访问时改成本机可达的域名,
|
||||
// 如 webui.example.com ⇒ <标签>.webui.example.com。
|
||||
|
||||
@ -20,13 +20,13 @@ import (
|
||||
)
|
||||
|
||||
// 通用反向代理:插件声明自带 HTTP 服务(plugin.json 的 proxies),
|
||||
// HomeAgent 按 **Host 子域标签** 把它们从 webui 的同一端口反代出去。
|
||||
// HologramAgent 按 **Host 子域标签** 把它们从 webui 的同一端口反代出去。
|
||||
//
|
||||
// ## 为什么是 Host 路由而不是路径前缀
|
||||
//
|
||||
// 插件自带 UI 普遍使用根绝对路径(实测 huawei_smarthome 的前端是
|
||||
// `api('/api/status')` → `fetch('/api/status')`)。若挂在 `/p/huawei/` 下,
|
||||
// 这些请求会打到 HomeAgent 自己的 `/api/status`,**静默错路由**。路径前缀方案
|
||||
// 这些请求会打到 HologramAgent 自己的 `/api/status`,**静默错路由**。路径前缀方案
|
||||
// 要么要求所有插件改前端,要么做 HTML/JS 内容重写——后者对拼进 JS 字符串的
|
||||
// 绝对路径只是"按概率能用",会产生「页面能开、某个按钮就坏」的静默故障。
|
||||
//
|
||||
@ -43,7 +43,7 @@ import (
|
||||
//
|
||||
// ## 认证
|
||||
//
|
||||
// 逐条由插件声明(ProxyAuthHomeAgent / ProxyAuthNone),默认 HomeAgent 统一保护。
|
||||
// 逐条由插件声明(ProxyAuthHomeAgent / ProxyAuthNone),默认 HologramAgent 统一保护。
|
||||
// 但 Host 路由下**子域与主门户不同源**,浏览器不会把门户的 homeagent_session
|
||||
// 发给子域——所以受保护模式下由反代层校验门户会话/API Key,校验通过后放行。
|
||||
// 详见 authorizeProxy。
|
||||
@ -658,7 +658,7 @@ func (h *Handler) authorizeProxy(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", `Bearer realm="homeagent"`)
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{
|
||||
"error": "该服务由 HomeAgent 统一保护,需要先登录门户或用 X-API-Key 访问",
|
||||
"error": "该服务由 HologramAgent 统一保护,需要先登录门户或用 X-API-Key 访问",
|
||||
"hint": "浏览器先访问门户登录;脚本用 -H 'X-API-Key: <key>' 或 ?__token=<key>",
|
||||
})
|
||||
return false
|
||||
|
||||
@ -17,7 +17,7 @@ import (
|
||||
// 访问不到那两个公共 CDN(内网 / 出口受限 / 断网 / 被墙)。
|
||||
//
|
||||
// 为什么必须本地化而不是「换个 CDN」:
|
||||
// 换 CDN 只是把同一个赌注重下一遍。HomeAgent 明确支持离线/内网部署,
|
||||
// 换 CDN 只是把同一个赌注重下一遍。HologramAgent 明确支持离线/内网部署,
|
||||
// 前端却有 4 个硬依赖在公网上 ⇒ 断网时星图必坏、且用户无从修复。
|
||||
// marked / DOMPurify 同理(Markdown 渲染与 XSS 净化)。
|
||||
//
|
||||
|
||||
@ -310,7 +310,7 @@ type BuildStatus struct {
|
||||
// SDKCompatible 是本内核可兼容的最高 SDK 版本。
|
||||
// 插件用旧 SDK 编译时靠它判断能不能加载。
|
||||
SDKCompatible string `json:"sdk_compatible"`
|
||||
// KernelName 是内核名(HomeAgent)。
|
||||
// KernelName 是内核名(HologramAgent)。
|
||||
KernelName string `json:"kernel_name"`
|
||||
// SourceURL 是本次构建对应的源码地址。AGPL-3.0 §13 要求:向使用者提供网络
|
||||
// 服务时,要给他们拿到 Corresponding Source 的机会——WebUI 状态页会把它渲染成
|
||||
|
||||
@ -69,7 +69,7 @@ func (d *Daemon) SetRestartHandler(fn func(id types.AgentID)) {
|
||||
}
|
||||
|
||||
func (d *Daemon) Start() error {
|
||||
log.Println("[homed] starting HomeAgent daemon")
|
||||
log.Println("[homed] starting HologramAgent daemon")
|
||||
|
||||
go d.nm.Start(d.ctx, d.cfg.Defaults.LLMEndpoints)
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ func (p MigratePlan) Script() string {
|
||||
unitPath := filepath.Join("/etc/systemd/system", UnitName)
|
||||
|
||||
fmt.Fprintf(&b, `#!/usr/bin/env bash
|
||||
# HomeAgent 数据目录迁移:%s → %s
|
||||
# HologramAgent 数据目录迁移:%s → %s
|
||||
#
|
||||
# ★ 这是**破坏性**操作。脚本已内置:
|
||||
# · 目标目录非空则拒绝执行
|
||||
|
||||
Reference in New Issue
Block a user