mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +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:
@ -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 净化)。
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user