mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
Merge pull request #26 from JianFeeeee/fix/webp-embed-and-migrate-log
fix(media): webp 解码支持 + 迁移失败可观测 + 补回遗漏的 module 路径统一
This commit is contained in:
14
go.mod
14
go.mod
@ -13,12 +13,12 @@ require github.com/yanyiwu/gojieba v1.4.7
|
||||
require github.com/yalue/onnxruntime_go v1.13.0
|
||||
|
||||
require (
|
||||
github.com/JianFeeeee/homeagentsdk v0.9.2
|
||||
github.com/JianFeeeee/hologramagentsdk v0.9.2
|
||||
github.com/charmbracelet/bubbles v1.0.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/text v0.3.8
|
||||
golang.org/x/text v0.23.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -41,6 +41,14 @@ require (
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/image v0.25.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/JianFeeeee/homeagentsdk => ./third_party/homeagent-sdk
|
||||
// SDK 仓 module 已改名为 hologramagentsdk(与 GitHub 仓名一致)。
|
||||
//
|
||||
// replace 指向本地源码副本,使主仓改动与 SDK 改动可在同一工作树内联调。
|
||||
// ★ 只能有这一条:Go 不允许同一目录被两个 module 路径 replace
|
||||
// (报 `used for two different module paths`)。所以主仓**所有** import 都已
|
||||
// 统一改为新名,旧的 homeagentsdk 路径不再保留。
|
||||
|
||||
replace github.com/JianFeeeee/hologramagentsdk => ./third_party/homeagent-sdk
|
||||
|
||||
4
go.sum
4
go.sum
@ -56,12 +56,16 @@ github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// eventLoop 已由 scheduler.go 的 schedulerLoop 取代(M2)。
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory/document"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 统一输入主干(processInput / resolveInput / injectedBlocks)与
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory/vector"
|
||||
safeclose "github.com/JianFeeeee/HologramAgent/internal/safeclose"
|
||||
)
|
||||
|
||||
@ -67,19 +69,41 @@ func (a *Agent) reembedStaleMedia() {
|
||||
|
||||
var jobsOnce sync.Once
|
||||
jobs := make(chan memory.MemoryBlock, workers*2)
|
||||
var done, failed int64
|
||||
var done, failed, unsupported int64
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// 失败原因按错误文本聚合 —— 不逐条打日志(上千张图会刷屏),
|
||||
// 但**必须留下"为什么失败"**。
|
||||
//
|
||||
// ★ 2026-10-23 实测教训:原先这里只 `failed++` 然后 continue,于是
|
||||
// 「向量迁移完成: 成功=0 失败=4」成了唯一的线索 —— 没有任何日志说明
|
||||
// 这 4 个是 webp(当时 chineseclip 未注册 webp 解码器)。失败不可观测
|
||||
// 直接导致这次排查要从数据库里反查 mime 才找到根因。
|
||||
var failMu sync.Mutex
|
||||
failReasons := map[string]int{}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for b := range jobs {
|
||||
if err := a.reembedOneBlock(b, fp); err != nil {
|
||||
atomic.AddInt64(&failed, 1)
|
||||
err := a.reembedOneBlock(b, fp)
|
||||
if err == nil {
|
||||
atomic.AddInt64(&done, 1)
|
||||
continue
|
||||
}
|
||||
atomic.AddInt64(&done, 1)
|
||||
// 模态不受支持是**永久**状态(不是本次网络/服务抖动):
|
||||
// 这类块永远不会有向量,语义检索也永远搜不到它。
|
||||
// 单独计数,避免它混在"失败"里让人以为重试就能好。
|
||||
if errors.Is(err, vector.ErrModalityUnsupported) {
|
||||
atomic.AddInt64(&unsupported, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&failed, 1)
|
||||
}
|
||||
key := b.MIME + " | " + err.Error()
|
||||
failMu.Lock()
|
||||
failReasons[key]++
|
||||
failMu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -93,8 +117,17 @@ func (a *Agent) reembedStaleMedia() {
|
||||
}
|
||||
safeclose.Safe(&jobsOnce, jobs)
|
||||
wg.Wait()
|
||||
log.Printf("[media] 向量迁移完成: 成功=%d 失败=%d 总计=%d fp=%s",
|
||||
done, failed, len(targets), shortFP(fp))
|
||||
log.Printf("[media] 向量迁移完成: 成功=%d 失败=%d 不支持=%d 总计=%d fp=%s",
|
||||
done, failed, unsupported, len(targets), shortFP(fp))
|
||||
// 逐类打印失败原因(聚合后通常只有 1~2 类)。
|
||||
// 没有这段,运维只能看到"失败=N"而不知为何 —— 本次 webp 问题的排查成本
|
||||
// 全花在这里。
|
||||
if len(failReasons) > 0 {
|
||||
log.Printf("[media] 失败原因分类(按 mime | error 聚合):")
|
||||
for reason, n := range failReasons {
|
||||
log.Printf("[media] %d 个: %s", n, reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reembedOneBlock 为一个图像块重算向量并写回。负载就在块里,无需外部取字节。
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -102,3 +103,100 @@ func TestReembedStaleMedia_NilSpaceNoop(t *testing.T) {
|
||||
t.Errorf("无空间可用时不该写向量: vec=%v model=%q", got.Vector, got.Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
// failSpace 让指定 mime 的图像嵌入失败,用来验证「失败必须留下原因」。
|
||||
type failSpace struct {
|
||||
failMIME string
|
||||
}
|
||||
|
||||
func (failSpace) VectorizeDense(string) ([]float64, error) { return []float64{1, 0}, nil }
|
||||
|
||||
func (f failSpace) EmbedImageDense(_ []byte, mime string) ([]float64, error) {
|
||||
if mime == f.failMIME {
|
||||
return nil, fmt.Errorf("模拟解码失败: %s", mime)
|
||||
}
|
||||
return []float64{1, 0}, nil
|
||||
}
|
||||
|
||||
func (failSpace) Fingerprint() string { return "fail-space" }
|
||||
func (failSpace) Dim() int { return 2 }
|
||||
func (failSpace) Loaded() bool { return true }
|
||||
func (failSpace) Close() {}
|
||||
|
||||
// 迁移失败必须留下**可诊断的原因**,而不只是一个计数。
|
||||
//
|
||||
// ★ 2026-10-23 生产事故:日志只有
|
||||
//
|
||||
// [media] 向量迁移完成: 成功=0 失败=4 总计=4
|
||||
//
|
||||
// 没有任何信息说明为什么。反查数据库才发现那 4 个块是 image/webp,
|
||||
// 而 provider 没注册 webp 解码器。**失败不可观测直接导致排查成本翻倍**。
|
||||
//
|
||||
// 本判据钉住:失败原因按 "mime | error" 聚合后必须出现在日志里。
|
||||
//
|
||||
// 变异:把 failReasons 的采集与打印删掉 ⇒ 红。
|
||||
func TestReembedStaleMedia_LogsFailureReasons(t *testing.T) {
|
||||
a := newMediaLoopAgent(t)
|
||||
// 两个都会失败(同一 mime)—— 应聚合成一条 "2 个: ..."
|
||||
for i := 0; i < 2; i++ {
|
||||
b := mediaBlock("image/webp", []byte("webp-bytes"))
|
||||
if err := a.memory.PutMemoryBlocks([]memory.MemoryBlock{b}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
logs := captureLog(t, func() {
|
||||
a.multimodalSpace = failSpace{failMIME: "image/webp"}
|
||||
a.reembedStaleMedia()
|
||||
})
|
||||
|
||||
if !strings.Contains(logs, "失败原因分类") {
|
||||
t.Fatalf("迁移失败时必须打印失败原因分类,实际日志:\n%s", logs)
|
||||
}
|
||||
if !strings.Contains(logs, "image/webp") {
|
||||
t.Fatalf("失败原因里必须出现具体 mime(否则无从定位是哪种格式),实际日志:\n%s", logs)
|
||||
}
|
||||
if !strings.Contains(logs, "模拟解码失败") {
|
||||
t.Fatalf("失败原因里必须带上 provider 返回的错误原文,实际日志:\n%s", logs)
|
||||
}
|
||||
// 计数必须出现(格式 "N 个: ...")—— 具体数字取决于去重后的块数,
|
||||
// 这里只要求"有计数",不绑死数字(绑死会让判据随去重策略变化而脆)。
|
||||
if !regexp.MustCompile(`\d+ 个: image/webp`).MatchString(logs) {
|
||||
t.Fatalf("失败原因应带聚合计数(形如「N 个: image/webp | ...」),实际日志:\n%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
// 不支持模态与临时失败必须**分开计数**:前者永远好不了(重试无意义),
|
||||
// 混在"失败"里会让人误以为重试即可恢复。
|
||||
func TestReembedStaleMedia_SeparatesUnsupportedFromFailed(t *testing.T) {
|
||||
a := newMediaLoopAgent(t)
|
||||
// 音频走 fakeSpace 的 ErrModalityUnsupported 分支;但 reembedStaleMedia
|
||||
// 只挑 image 模态,所以用图像 mime + 让 provider 返回 unsupported 来验证分类。
|
||||
img := mediaBlock("image/tiff", []byte("tiff-bytes"))
|
||||
if err := a.memory.PutMemoryBlocks([]memory.MemoryBlock{img}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
logs := captureLog(t, func() {
|
||||
a.multimodalSpace = unsupportedImageSpace{}
|
||||
a.reembedStaleMedia()
|
||||
})
|
||||
|
||||
if !strings.Contains(logs, "不支持=1") {
|
||||
t.Fatalf("不受支持的模态应单独计数(不支持=1),实际日志:\n%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
// unsupportedImageSpace 对任何图像都返回 ErrModalityUnsupported。
|
||||
type unsupportedImageSpace struct{}
|
||||
|
||||
func (unsupportedImageSpace) VectorizeDense(string) ([]float64, error) { return []float64{1, 0}, nil }
|
||||
|
||||
func (unsupportedImageSpace) EmbedImageDense(_ []byte, mime string) ([]float64, error) {
|
||||
return nil, fmt.Errorf("%w: %s", vector.ErrModalityUnsupported, mime)
|
||||
}
|
||||
|
||||
func (unsupportedImageSpace) Fingerprint() string { return "unsupported-space" }
|
||||
func (unsupportedImageSpace) Dim() int { return 2 }
|
||||
func (unsupportedImageSpace) Loaded() bool { return true }
|
||||
func (unsupportedImageSpace) Close() {}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// sceneKeysFor 推导本轮输入的**当前场景**。
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/meta"
|
||||
sdkmeta "github.com/JianFeeeee/homeagentsdk/meta"
|
||||
sdkmeta "github.com/JianFeeeee/hologramagentsdk/meta"
|
||||
)
|
||||
|
||||
func TestExpandPromptVars(t *testing.T) {
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 这一组测试锁死「默认不裁剪」这条语义。
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 这一组测试锁死「默认召回、可显式关闭」这条语义。
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
func scenePolicyNoneEvent() *agentIO.InputEvent {
|
||||
|
||||
@ -33,7 +33,7 @@ import (
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Step 是任务状态机的游标。
|
||||
|
||||
@ -42,7 +42,7 @@ import (
|
||||
|
||||
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// visionBatchProvider 在 batchProvider 之上声明「能看图」。
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/JianFeeeee/HologramAgent/internal/meta"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/security"
|
||||
sdkmeta "github.com/JianFeeeee/homeagentsdk/meta"
|
||||
sdkmeta "github.com/JianFeeeee/hologramagentsdk/meta"
|
||||
)
|
||||
|
||||
func (a *Agent) buildMemoryContext(input string, maxTokens int, scenes []string) string {
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
|
||||
sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// appendPlaceholder 复刻 process() 循环顶部的补位逻辑。
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// ErrToolNotFound 表示「工具不存在」(未注册 / 所属插件已卸载或崩溃)。
|
||||
|
||||
@ -3,7 +3,7 @@ package io
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// drainOne 取出一条注入事件;没有则 Fatal。
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
-- HomeAgent Lua Plugin SDK
|
||||
-- Interface contract between Lua plugins and HomeAgent kernel.
|
||||
-- HologramAgent Lua Plugin SDK
|
||||
-- Interface contract between Lua plugins and HologramAgent kernel.
|
||||
-- !impl functions are replaced by Go implementations at runtime.
|
||||
-- Standalone/debug: pure Lua mock implementations are used.
|
||||
-- Usage: local sdk = require("sdk")
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugin/proc"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// EventRing 是 Bus 与 proc.EvtRing 之间的适配层。
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugin/proc"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// TestEventRing_CloseUnsubscribesFromBus 钉死:EventRing.Close 必须把
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugin/proc"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 事件环基础测试:Host 创建事件环 → EventRing 写入 → 消费者读到。
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugin/proc"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
const PackageExt = ".hmap"
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 子进程架构的性能基准(Part 6.6 验收项)。
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// coreHandler 把插件发来的 RPC 调用路由到内核 PluginSDK。
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/knowledge"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// handleGraphMemory 处理图记忆(实体/关系):recall / commit / introspect / merge / purge。
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// handleRegister 处理注册面:工具 / stage / 输出通道 / 插件 API / 入站通道。
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// handlePluginMgr 处理插件管理:reloadOne / listLoaded / isDisabled。
|
||||
|
||||
@ -22,7 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
func (h *coreHandler) secrets(method string, params json.RawMessage) (interface{}, error) {
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// handleSettings 处理全部设置项读写(14 个 method 共用一条实现)。
|
||||
|
||||
@ -24,7 +24,7 @@ import (
|
||||
|
||||
internalConfig "github.com/JianFeeeee/HologramAgent/internal/config"
|
||||
internalSDK "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// secretE2EPlugin 是一个**用真实模板**编译的插件:它只做两件事 ——
|
||||
@ -32,7 +32,7 @@ import (
|
||||
const secretE2EPlugin = `package main
|
||||
|
||||
import (
|
||||
sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
type secretPlugin struct{ name string }
|
||||
|
||||
@ -29,14 +29,14 @@ import (
|
||||
internalConfig "github.com/JianFeeeee/HologramAgent/internal/config"
|
||||
internalSDK "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
internalSecurity "github.com/JianFeeeee/HologramAgent/internal/security"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// secretsE2EPlugin 用真实模板编译:只做变量库往返。
|
||||
const secretsE2EPlugin = `package main
|
||||
|
||||
import (
|
||||
sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
type secPlugin struct{ name string }
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 端到端:用**真实 hmapdev 模板**编译的插件,经内核 proc 通道加载运行。
|
||||
@ -25,7 +25,7 @@ const e2ePluginSource = `package main
|
||||
import (
|
||||
"strings"
|
||||
|
||||
sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
type e2ePlugin struct{ name string }
|
||||
@ -148,8 +148,8 @@ func buildPluginWithRealTemplate(t *testing.T, businessCode string) string {
|
||||
}
|
||||
mustWriteFile(t, filepath.Join(dir, "go.mod"),
|
||||
"module e2eplugin\n\ngo 1.25\n\n"+
|
||||
"require github.com/JianFeeeee/homeagentsdk v0.9.2\n\n"+
|
||||
"replace github.com/JianFeeeee/homeagentsdk => "+sdkPath+"\n")
|
||||
"require github.com/JianFeeeee/hologramagentsdk v0.9.2\n\n"+
|
||||
"replace github.com/JianFeeeee/hologramagentsdk => "+sdkPath+"\n")
|
||||
|
||||
bin := filepath.Join(dir, "plugin.bin")
|
||||
cmd := exec.Command("go", "build", "-o", bin, ".")
|
||||
@ -241,7 +241,7 @@ func TestE2E_RealTemplatePluginFullLifecycle(t *testing.T) {
|
||||
func TestE2E_RealTemplateReadOnlyPluginDoesNotOverwrite(t *testing.T) {
|
||||
const readerSource = `package main
|
||||
|
||||
import sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
import sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
|
||||
type readerPlugin struct{ name string }
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
safeclose "github.com/JianFeeeee/HologramAgent/internal/safeclose"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// ---- 事件类型编码(编译时确定,与 pubsdk.EventType 一一对应)----
|
||||
|
||||
@ -21,7 +21,7 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
func newTestSeg(t *testing.T) *Segment {
|
||||
|
||||
@ -45,7 +45,7 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
type gcPlugin struct{ name string }
|
||||
@ -378,7 +378,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
sdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
type gcPlugin struct{ name string }
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// Host 持有**被全部子进程插件共享的统一内存区域**,是共享数据面的
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// TestHost_CloseWaitsForInflightStage 钉死不变量:
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// stubSubscriber 是最小 EvtRingSubscriber 实现,用于验证「关停时统一退订」。
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 本文件是 2026-09-04 06:56:18 线上 crash 的回归测试。
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// Plugin 是 registry 可加载的子进程插件,与内置插件同构的启停接口。
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 端到端验证:内核 RunStage 并发扇出 → 真实子进程插件经共享内存读改写 → 结果回读。
|
||||
|
||||
@ -9,7 +9,7 @@ package proc
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
func TestClampExternalPriority_RejectsL4(t *testing.T) {
|
||||
|
||||
@ -22,7 +22,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// profileCtx 构造一组「接近真实」的 StageContext。
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 本文件验证共享内存 stage 并发的正确性——**整个迁移最关键的一环**(§4.4 风险 3.4)。
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// StageContext 的跨进程编解码(§3.3 数据面 / §3.4 SDK 封装全部复杂度)。
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// stage 执行:把内核的 RunStage 并发扇出接到共享段(§3.4,风险 3.4 的落点)。
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// qq 插件「重复申请 stage 锁」的生产诊断与修复(2026-10-07)
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 流式输出压测(§4.3 标记「风险高」的那一项)。
|
||||
|
||||
@ -3,7 +3,7 @@ package plugin
|
||||
import (
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugin/proc"
|
||||
isdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// procCore 把内核的 *internal/sdk.PluginSDK 收窄成子进程插件可见的能力面。
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
sdkpub "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
sdkpub "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
type toolCapture struct {
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// mockSettings implements pubsdk.SettingsAPI for tests
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 真实外部插件(重编为 plugin.bin)经内核加载后的端到端冒烟(Part 6.3)。
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
@ -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。
|
||||
|
||||
@ -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 源 / 变更追踪。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package sdk
|
||||
|
||||
import pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
import pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
|
||||
// KernelControlAPI 是内核重启/关机通道(转发公开 SDK 的同名接口)。
|
||||
//
|
||||
|
||||
@ -2,7 +2,7 @@ package sdk
|
||||
|
||||
import (
|
||||
"github.com/JianFeeeee/HologramAgent/internal/knowledge"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// KnowledgeAPI 是内置插件使用的全量知识库接口。
|
||||
|
||||
@ -3,7 +3,7 @@ package sdk
|
||||
import (
|
||||
"context"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// LLMAPI 是内置插件使用的全量 LLM Provider 接口。
|
||||
|
||||
@ -3,7 +3,7 @@ package sdk
|
||||
import (
|
||||
"time"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// MemoryBlockView 是块的最小只读视图(供星图轮询端点)。
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
|
||||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// SDKVersion 是对外 SDK 版本号,与核心 meta.Version 保持一致。
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// ProxyDef 是一条反代声明(见 pubsdk.ProxyDef 的完整文档,含「单一入口原则」)。
|
||||
|
||||
@ -28,7 +28,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// RawFilesDirName 是包内资源目录名(与 hmapdev 的 RawFilesPrefix 一致)。
|
||||
|
||||
@ -22,7 +22,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
internalSecurity "github.com/JianFeeeee/HologramAgent/internal/security"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// secretsImpl 实现 pubsdk.SecretsAPI。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package sdk
|
||||
|
||||
import pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
import pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
|
||||
// SettingsAPI 是内置插件使用的全量配置接口。
|
||||
type SettingsAPI interface {
|
||||
|
||||
@ -6,6 +6,17 @@ import (
|
||||
"image"
|
||||
|
||||
// 契约要求 provider 自行解码 Data,所以这里注册常见图像格式。
|
||||
//
|
||||
// ★ webp 必须注册(2026-10-23 实测):线上 551 个图像块里有 4 个是
|
||||
// `image/webp`(由 see_image 抓取的第三方图片),它们**永远嵌不进去** ——
|
||||
// 启动时向量迁移报「失败=4」,但错误被 continue 吞掉、没有任何日志,
|
||||
// 只看到数字对不上。根因就是这里少了 webp 注册:
|
||||
//
|
||||
// image.Decode(...) → "image: unknown format"
|
||||
//
|
||||
// Go 标准库不含 webp 解码器(那是 x/image 的子包),所以这是新增依赖的
|
||||
// 唯一理由。x/image 是纯 Go、无 cgo,不改变二进制形态。
|
||||
_ "golang.org/x/image/webp"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
97
providers/chineseclip/webp_test.go
Normal file
97
providers/chineseclip/webp_test.go
Normal file
@ -0,0 +1,97 @@
|
||||
package chineseclip
|
||||
|
||||
// webp 解码支持与「失败必须可观测」的判据。
|
||||
//
|
||||
// ## 为什么这两条要放在这里
|
||||
//
|
||||
// 2026-10-23 线上排查:启动日志只有一行
|
||||
//
|
||||
// [media] 向量迁移完成: 成功=0 失败=4 总计=4
|
||||
//
|
||||
// 没有任何信息说明为什么失败。反查数据库才发现那 4 个块的 mime 是
|
||||
// `image/webp`,而本 provider 只注册了 gif/jpeg/png 三个解码器 ⇒
|
||||
// `image.Decode` 报 "unknown format" ⇒ 它们**永远**拿不到向量,
|
||||
// 语义检索也永远搜不到它们。
|
||||
//
|
||||
// 两个独立缺陷:
|
||||
// 1. 功能缺口:webp 不可解码
|
||||
// 2. 可观测性缺口:失败被 `continue` 吞掉,只有计数没有原因
|
||||
//
|
||||
// 判据分开钉住,避免以后只修其中一个。
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// webpBytes 是一个最小的合法 webp 文件(1×1 白色,RIFF/WEBP/VP8L)。
|
||||
//
|
||||
// 手写而不依赖测试夹具:夹具文件容易被误删,而"能解码 webp"这件事
|
||||
// 必须一直成立 —— 线上确实存在 webp 记忆块。
|
||||
var webpBytes = []byte{
|
||||
// 由 ffmpeg 生成的真实 2×2 白色 webp(44 字节,VP8 有损编码)。
|
||||
// 手写字节序列容易构造出不合法容器 —— 必须用真实编码器产物。
|
||||
0x52, 0x49, 0x46, 0x46, 0x24, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
|
||||
0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x9d,
|
||||
0x01, 0x2a, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa4, 0x00,
|
||||
0x03, 0x70, 0x00, 0xfe, 0xfb, 0x94, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// ① webp 必须能被解码(否则 memory_blocks 里的 webp 块永远无向量)。
|
||||
//
|
||||
// 变异:注释掉 image.go 里的 `_ "golang.org/x/image/webp"` ⇒ 红。
|
||||
func TestWebpIsDecodable(t *testing.T) {
|
||||
_, format, err := image.Decode(bytes.NewReader(webpBytes))
|
||||
if err != nil {
|
||||
t.Fatalf("webp 解码失败: %v —— image.go 里缺少 _ \"golang.org/x/image/webp\""+
|
||||
"(线上 4 个 webp 记忆块因此永远拿不到向量)", err)
|
||||
}
|
||||
if format != "webp" {
|
||||
t.Fatalf("格式识别错误: got %q want \"webp\"", format)
|
||||
}
|
||||
}
|
||||
|
||||
// ② webp 必须能走完整的预处理链路(不只是解码,还要变成 NCHW 张量)。
|
||||
//
|
||||
// 只验解码不够:预处理里有缩放/归一化,任何一步对 webp 不适用都算没支持。
|
||||
func TestWebpPreprocessProducesTensor(t *testing.T) {
|
||||
const size = 32
|
||||
mean := []float64{0.48145466, 0.4578275, 0.40821073}
|
||||
std := []float64{0.26862954, 0.26130258, 0.27577711}
|
||||
|
||||
tensor, err := preprocessImage(webpBytes, size, mean, std)
|
||||
if err != nil {
|
||||
t.Fatalf("webp 预处理失败: %v", err)
|
||||
}
|
||||
if want := 3 * size * size; len(tensor) != want {
|
||||
t.Fatalf("张量长度 %d,期望 %d(3×%d×%d)", len(tensor), want, size, size)
|
||||
}
|
||||
// 1×1 白色图放大后应接近 (1-mean)/std 的白点值;只验非全零,
|
||||
// 具体像素值由 jpeg/png 的测试覆盖(同一套代码路径)。
|
||||
nonZero := false
|
||||
for _, v := range tensor {
|
||||
if v != 0 {
|
||||
nonZero = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !nonZero {
|
||||
t.Fatal("张量全为零 —— 预处理没有真正读到像素")
|
||||
}
|
||||
}
|
||||
|
||||
// ③ png 仍必须可用(防止注册表改动把其它格式弄坏)。
|
||||
func TestPngStillDecodable(t *testing.T) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||
img.Set(0, 0, color.RGBA{255, 255, 255, 255})
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, img); err != nil {
|
||||
t.Fatalf("构造 png 失败: %v", err)
|
||||
}
|
||||
if _, format, err := image.Decode(&buf); err != nil || format != "png" {
|
||||
t.Fatalf("png 解码异常: format=%q err=%v", format, err)
|
||||
}
|
||||
}
|
||||
2
third_party/homeagent-sdk/go.mod
vendored
2
third_party/homeagent-sdk/go.mod
vendored
@ -1,3 +1,3 @@
|
||||
module github.com/JianFeeeee/homeagentsdk
|
||||
module github.com/JianFeeeee/hologramagentsdk
|
||||
|
||||
go 1.21.0
|
||||
|
||||
4
third_party/homeagent-sdk/sdk/lua/sdk.lua
vendored
4
third_party/homeagent-sdk/sdk/lua/sdk.lua
vendored
@ -1,5 +1,5 @@
|
||||
-- HomeAgent Lua Plugin SDK
|
||||
-- Interface contract between Lua plugins and HomeAgent kernel.
|
||||
-- HologramAgent Lua Plugin SDK
|
||||
-- Interface contract between Lua plugins and HologramAgent kernel.
|
||||
-- !impl functions are replaced by Go implementations at runtime.
|
||||
-- Standalone/debug: pure Lua mock implementations are used.
|
||||
-- Usage: local sdk = require("sdk")
|
||||
|
||||
2
third_party/homeagent-sdk/sdk/plugin.go
vendored
2
third_party/homeagent-sdk/sdk/plugin.go
vendored
@ -3,7 +3,7 @@ package sdk
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/JianFeeeee/homeagentsdk/meta"
|
||||
"github.com/JianFeeeee/hologramagentsdk/meta"
|
||||
)
|
||||
|
||||
// SDKVersion 是对外暴露的 SDK 版本号。
|
||||
|
||||
22
third_party/homeagent-sdk/sdk/proxy.go
vendored
22
third_party/homeagent-sdk/sdk/proxy.go
vendored
@ -6,20 +6,20 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 反向代理声明:插件告诉 HomeAgent「我起了个 HTTP 服务,请把它反代出去」。
|
||||
// 反向代理声明:插件告诉 HologramAgent「我起了个 HTTP 服务,请把它反代出去」。
|
||||
//
|
||||
// 为什么需要:插件自带 Web UI / HTTP API 时,监听地址在插件自己的配置里
|
||||
// (如 127.0.0.1:12100),外部无从得知;而 webui 的对外端口通常只有一个
|
||||
// (默认 :8080,且常经 frp 单端口隧道穿透)。没有声明机制时,用户只能
|
||||
// 「知道端口 + 自己配转发」,插件换端口就失效。
|
||||
//
|
||||
// 设计取舍——**声明式而非注册式**:声明写在 plugin.json 里,由 HomeAgent
|
||||
// 设计取舍——**声明式而非注册式**:声明写在 plugin.json 里,由 HologramAgent
|
||||
// 在加载插件时读取聚合,而不是让插件在运行期调 API 注册。理由:
|
||||
// 1. 静态可发现:未启动/已崩溃的插件,其服务声明依然可见(可给出准确报错
|
||||
// 「插件 X 声明了 ui 但目标 127.0.0.1:12100 不可达」,而不是静默 404);
|
||||
// 2. 可版本化:声明随插件包一起分发、可 diff、可审计;
|
||||
// 3. 旧内核无害:manifest 解析忽略未知字段,未支持该能力的 HomeAgent 读旧
|
||||
// 插件、或旧 HomeAgent 读新插件都不会报错。
|
||||
// 3. 旧内核无害:manifest 解析忽略未知字段,未支持该能力的 HologramAgent 读旧
|
||||
// 插件、或旧 HologramAgent 读新插件都不会报错。
|
||||
//
|
||||
// 与 ToolDef / ChannelDef / ConfigDef 同族:SDK 定义声明契约,内核实现行为。
|
||||
// 声明方式与其它能力一致 —— 在 Start() 里调 RegisterProxy(name, def),
|
||||
@ -64,7 +64,7 @@ import (
|
||||
type ProxyDef struct {
|
||||
// Name 是同一插件内多条声明的唯一标识(如 "ui"、"api")。
|
||||
// 运行期由 RegisterProxy 的第一个参数填入;声明式由 plugin.json 的
|
||||
// name 键填入。省略时由 HomeAgent 兜底为 "service"。
|
||||
// name 键填入。省略时由 HologramAgent 兜底为 "service"。
|
||||
Name string `json:"name,omitempty"`
|
||||
// Host 是**子域名标签**(不含基域名),如 "huawei" 对应 huawei.<基域名>。
|
||||
//
|
||||
@ -72,12 +72,12 @@ type ProxyDef struct {
|
||||
// (DNS label 规则)。省略时默认取插件名(下划线转连字符,因为下划线
|
||||
// 不是合法 DNS label 字符)。
|
||||
//
|
||||
// 冲突处理:两个插件声明同一 Host 时,HomeAgent 不做「后者覆盖前者」——
|
||||
// 冲突处理:两个插件声明同一 Host 时,HologramAgent 不做「后者覆盖前者」——
|
||||
// 那样会让先声明者静默消失。冲突条目被拒绝并在反代表里记录原因。
|
||||
Host string `json:"host,omitempty"`
|
||||
|
||||
// Target 是上游地址,形如 "127.0.0.1:12100" 或 "http://127.0.0.1:12100"。
|
||||
// 可带路径前缀(如 "127.0.0.1:3000/base"),HomeAgent 转发时保留该前缀。
|
||||
// 可带路径前缀(如 "127.0.0.1:3000/base"),HologramAgent 转发时保留该前缀。
|
||||
//
|
||||
// 端口由插件自己填它**实际监听**的地址,避免「声明与实际漂移」。
|
||||
Target string `json:"target"`
|
||||
@ -135,7 +135,7 @@ type ProxyDef struct {
|
||||
//
|
||||
// 为什么做成可声明项:设备网关(remotedevice)这类服务的调用方是**设备**,
|
||||
// 它们不可能持有浏览器会话 cookie,而服务自身已有接入令牌(如 ws_token)。
|
||||
// 强制走 HomeAgent 门户鉴权会把这类链路挡死;反过来,插件自带的 UI 若
|
||||
// 强制走 HologramAgent 门户鉴权会把这类链路挡死;反过来,插件自带的 UI 若
|
||||
// 声明 none,就等于把管理界面裸露给任何能访问该端口的人。
|
||||
// 因此必须由插件**逐条**声明,而不是全局一刀切。
|
||||
Auth string `json:"auth,omitempty"`
|
||||
@ -198,7 +198,7 @@ func EffectiveProxyAuth(auth string) string {
|
||||
|
||||
// ValidProxyHostLabel 校验子域名标签是否合法(DNS label 规则)。
|
||||
//
|
||||
// 独立成导出函数:插件作者在写声明时、HomeAgent 在加载时、工具链在打包时
|
||||
// 独立成导出函数:插件作者在写声明时、HologramAgent 在加载时、工具链在打包时
|
||||
// 都要用同一套规则判定,避免三处各写一份而互相不一致。
|
||||
func ValidProxyHostLabel(label string) bool {
|
||||
if label == "" || len(label) > 63 {
|
||||
@ -246,7 +246,7 @@ func NormalizeProxyHost(pluginName string) string {
|
||||
|
||||
// ValidateProxyDef 校验一条反代声明,返回人类可读的错误说明(合法时为空)。
|
||||
//
|
||||
// 为什么要在 SDK 里做校验:HomeAgent 加载插件时必须能明确拒绝坏声明并说明
|
||||
// 为什么要在 SDK 里做校验:HologramAgent 加载插件时必须能明确拒绝坏声明并说明
|
||||
// 原因(而不是静默忽略导致用户以为配好了);插件作者也需要在本地就能查出
|
||||
// 拼错的 Target/Host。同一套规则两端共用。
|
||||
func ValidateProxyDef(d ProxyDef) string {
|
||||
@ -338,7 +338,7 @@ func (s *PluginSDK) SetProxyRegistrar(r ProxyRegistrar) {
|
||||
s.apiMu.Unlock()
|
||||
}
|
||||
|
||||
// RegisterProxy 声明一个需要 HomeAgent 反代出去的服务。
|
||||
// RegisterProxy 声明一个需要 HologramAgent 反代出去的服务。
|
||||
//
|
||||
// 与 RegisterTool / RegisterInputChannel / RegisterOutputChannel 同一风格:
|
||||
// 显式给名字 + 声明体。名字用于展示、日志与冲突提示(不参与路由 —— 路由键是
|
||||
|
||||
42
third_party/homeagent-sdk/sdk/proxy_test.go
vendored
42
third_party/homeagent-sdk/sdk/proxy_test.go
vendored
@ -6,25 +6,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 存量 plugin.json 里写的是旧值 "homeagent"(本机实测 huawei_smarthome)。
|
||||
// 改名后必须**继续接受并归一化**它 —— 否则那些插件在加载时被判为非法声明,
|
||||
// 反代直接不生效,症状是"插件好好的但页面打不开",且没有任何报错指向改名。
|
||||
func TestProxyAuthLegacyValueStillAccepted(t *testing.T) {
|
||||
if !ValidProxyAuth(ProxyAuthAgentLegacy) {
|
||||
t.Fatalf("旧值 %q 必须仍被接受", ProxyAuthAgentLegacy)
|
||||
}
|
||||
if ProxyAuthAgentLegacy == ProxyAuthAgent {
|
||||
t.Fatal("旧值应与新值不同,否则本判据测不到东西")
|
||||
}
|
||||
// ★ 归一化是最关键的一步:下游一律只可能拿到新值,
|
||||
// 否则每个判断点都要写两遍比较,漏一处就是鉴权绕过。
|
||||
if got := EffectiveProxyAuth(ProxyAuthAgentLegacy); got != ProxyAuthAgent {
|
||||
t.Fatalf("旧值应归一化为 %q,实际 %q", ProxyAuthAgent, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyAuthDefaultsToHomeAgent(t *testing.T) {
|
||||
// 空串必须归一化为「HomeAgent 统一保护」——这是安全默认。
|
||||
func TestProxyAuthDefaultsToHologramAgent(t *testing.T) {
|
||||
// 空串必须归一化为「HologramAgent 统一保护」——这是安全默认。
|
||||
// 若哪天有人把默认改成 none,这条会立刻红。
|
||||
if got := EffectiveProxyAuth(""); got != ProxyAuthAgent {
|
||||
t.Fatalf("空 auth 应归一化为 %q,实际 %q", ProxyAuthAgent, got)
|
||||
@ -44,6 +27,27 @@ func TestProxyAuthDefaultsToHomeAgent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 存量 plugin.json 里写的是旧值 "homeagent"(本机实测 huawei_smarthome)。
|
||||
// 改名后必须**继续接受并归一化**它 —— 否则那些插件在加载时被判为非法声明,
|
||||
// 反代直接不生效,症状是"插件好好的但页面打不开",且没有任何报错指向改名。
|
||||
func TestProxyAuthLegacyValueStillAccepted(t *testing.T) {
|
||||
if !ValidProxyAuth(ProxyAuthAgentLegacy) {
|
||||
t.Fatalf("旧值 %q 必须仍被接受", ProxyAuthAgentLegacy)
|
||||
}
|
||||
if ProxyAuthAgentLegacy == ProxyAuthAgent {
|
||||
t.Fatal("旧值应与新值不同,否则本判据测不到东西")
|
||||
}
|
||||
// ★ 归一化是最关键的一步:下游一律只可能拿到新值,
|
||||
// 否则每个判断点都要写两遍比较,漏一处就是鉴权绕过。
|
||||
if got := EffectiveProxyAuth(ProxyAuthAgentLegacy); got != ProxyAuthAgent {
|
||||
t.Fatalf("旧值应归一化为 %q,实际 %q", ProxyAuthAgent, got)
|
||||
}
|
||||
// 存量 plugin.json 的字面量必须能过校验(这是真实数据,不是假设)。
|
||||
if !ValidProxyAuth("homeagent") {
|
||||
t.Fatal(`存量 plugin.json 里的 "auth": "homeagent" 必须仍被接受`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidProxyHostLabel(t *testing.T) {
|
||||
legit := []string{"huawei", "a", "a-b", "abc123", "0", "x" + string(make([]byte, 0)) + "yz"}
|
||||
for _, s := range legit {
|
||||
|
||||
Reference in New Issue
Block a user