Merge remote-tracking branch 'refs/remotes/gh/main' into fix/webui-gzip-race

This commit is contained in:
JianFeeeee
2026-10-24 13:25:18 +08:00
300 changed files with 5251 additions and 931 deletions

View File

@ -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)
}

View File

@ -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>

View File

@ -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") +

View File

@ -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 &middot; 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 &middot; 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'))});

View File

@ -10,7 +10,7 @@ import (
"github.com/JianFeeeee/HologramAgent/internal/memory"
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
)
// fakeGraphMemory 是一个最小 MemoryAPI 桩,只为把 GraphData 喂给 handler。

View File

@ -15,7 +15,7 @@ import (
"github.com/JianFeeeee/HologramAgent/internal/knowledge"
"github.com/JianFeeeee/HologramAgent/internal/memory"
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
)
// 记忆面:图记忆 / 文档记忆 / 文本记忆 / 知识库 / LLM 源 / 变更追踪。

View File

@ -363,7 +363,7 @@ type openAIMessage struct {
// OpenAI 客户端把它当作能力探测(probe)。请求方要的不是模型清单本身,
// 而是一个「这个端点讲 OpenAI 协议」的确认。
//
// 所以这里返回 HomeAgent 自身作为唯一条目:调用方无论填哪个 id 都会
// 所以这里返回 HologramAgent 自身作为唯一条目:调用方无论填哪个 id 都会
// 被路由到同一个 agent(本端点的 model 参数确实只是透传)。
// 不谎报 GPT 之类的名字 —— 那会让用户以为能选模型,而实际不能。
func (h *Handler) handleOpenAIModels(w http.ResponseWriter, r *http.Request) {

View File

@ -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)

View File

@ -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")
}
}

View File

@ -140,7 +140,7 @@ func TestOpenAIModelsRequiresAuth(t *testing.T) {
// 假流式实现 → 先同步等 InjectTextSync 返回(被扣住,阻塞数秒),
// 等不到就什么都发不出去 → 首帧迟到数秒。
//
// 所以断言「首帧远早于总计 700ms 的生成时间」<EFBFBD><EFBFBD>就是判据的全部。
// 所以断言「首帧远早于总计 700ms 的生成时间」就是判据的全部。
func TestOpenAIStreamIsActuallyStreaming(t *testing.T) {
srv, iom, bus := newOpenAITestServer(t)

View File

@ -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。

View File

@ -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。
@ -589,7 +589,10 @@ func (h *Handler) serveProxyHost(w http.ResponseWriter, r *http.Request) bool {
})
return true
}
if rt.Auth == sdk.ProxyAuthHomeAgent && !h.authorizeProxy(w, r) {
// 用 EffectiveProxyAuth 而不是直接比常量:存量 plugin.json 里可能写着
// 旧值(ProxyAuthAgentLegacy)。直接比常量 ⇒ 旧值被判为"非门户鉴权"
// ⇒ **鉴权被绕过**,这是本仓最不能出的错。
if sdk.EffectiveProxyAuth(rt.Auth) == sdk.ProxyAuthAgent && !h.authorizeProxy(w, r) {
return true
}
if rt.reverse == nil {
@ -622,7 +625,7 @@ func (h *Handler) serveProxyHost(w http.ResponseWriter, r *http.Request) bool {
return true
}
if route.Auth == sdk.ProxyAuthHomeAgent && !h.authorizeProxy(w, r) {
if sdk.EffectiveProxyAuth(route.Auth) == sdk.ProxyAuthAgent && !h.authorizeProxy(w, r) {
return true // 已写 401
}
@ -658,7 +661,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
@ -842,7 +845,9 @@ func parseManualRoutes(text string) []proxyDecl {
out = append(out, proxyDecl{
Plugin: "manual", Name: "invalid", Host: "invalid",
Target: "",
Auth: "homeagent",
// 非法行占位:用默认安全值(不是裸字面量 —— 契约值改名时
// 裸字面量会被漏掉,届时这里悄悄变成"无鉴权")。
Auth: sdk.ProxyAuthAgent,
})
continue
}

View File

@ -17,7 +17,7 @@ import (
// 访问不到那两个公共 CDN(内网 / 出口受限 / 断网 / 被墙)。
//
// 为什么必须本地化而不是「换个 CDN」:
// 换 CDN 只是把同一个赌注重下一遍。HomeAgent 明确支持离线/内网部署,
// 换 CDN 只是把同一个赌注重下一遍。HologramAgent 明确支持离线/内网部署,
// 前端却有 4 个硬依赖在公网上 ⇒ 断网时星图必坏、且用户无从修复。
// marked / DOMPurify 同理(Markdown 渲染与 XSS 净化)。
//