mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
★ 上一轮清理被合并覆盖:GUI 分支含 DESKTOP-PET-NOTES.md /
SERVER-HANDOFF.md / GUI-PLAN.md,合并时取了我方版本,
但我方 main 已是 squash 产物 ⇒ 三个文件复活。
而我当时只核对了 benchmark.md 一个就宣布成功 —— 判断依据不足。
本轮逐个文件验证(不再抽样)。
删除(零代码引用)
DESKTOP-PET-NOTES.md 外部产品(Coopanion)拆解笔记
SERVER-HANDOFF.md 给服务端的待办清单,已过时
napcat-design-dna.json 设计 DNA 数据
tmp_jinabench.py 临时脚本
tmp_benchmark/ 临时跑分程序
归位(不是删)
plan.md → docs/zh/legacy-issues.md (遗留问题清单,有价值)
GUI-PLAN.md → cmd/gui/GUI-PLAN.md (含坐标换算/koffi 踩坑)
根目录那份与 cmd/gui/ 已有副本字节相同(285b84dd…),删前者
deploy-plan.sh → deploy/scripts/
deploy-waiter.sh → deploy/scripts/
deploy-sdk-site.sh → deploy/scripts/
脱敏(deploy-plan.sh 与 deploy-sdk-site.sh 含本机路径)
/home/newqqagent → ${HA_DATA}
/home/program/TrueAgent → ${REPO}
/home/program → ${HOME_DIR}
引用同步:27 处(docs/zh/deploy-runbook.md、toolcall-parallel-execution-plan.md、
scripts/capability-bench/PLAN.md、三个脚本自身的用法注释、ci.yml 注释)
根目录现状:仅 README.md / README_EN.md / LICENSE / Makefile /
go.mod / go.sum / .gitignore / .golangci.yml / .mailmap / .pi-lens.json
验证:go build ✅ 三个 sh 的 bash -n ✅
go test -short ./internal/... ./pkg/... ./cmd/... 全绿
根目录文件清单逐个核对(10 项,全为门面/配置)
Co-authored-by: pi <pi@local>
133 lines
4.8 KiB
Go
133 lines
4.8 KiB
Go
package remotedevice
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// clipboardsue 的 content 参数:从**服务端可见的路径**读入剪切板。
|
||
//
|
||
// ## 为什么要它(2026-10-05)
|
||
//
|
||
// 长文本/代码块塞不进 `clipboardsue <文字>` 的命令行;让模型先生成一大段
|
||
// 文本再原样传给设备,既烧 token 又容易在转述中失真。
|
||
// 与 GUI 侧 screensue / speakeruse 的 `@<路径>` 是同一类问题的同一类解法。
|
||
//
|
||
// ## 这些判据钉住的行为
|
||
//
|
||
// ① text 与 content 都空 ⇒ 明确报错(不能静默写空剪切板)
|
||
// ② content 指向不存在的文件 ⇒ 报错且**带上路径**(否则模型无从修正)
|
||
// ③ content 超上限 ⇒ 报错(防止把会话撑爆)
|
||
// ④ 设备不可用时优先报设备错,而不是先读文件
|
||
// ——顺序很重要:不然一个离线设备会伪装成「文件不存在」
|
||
//
|
||
// 注:本文件只验**参数校验与错误路径**。真正读文件并下发需要一台在线设备,
|
||
// 那由 binary_test.go 的 WS 级联调覆盖。
|
||
|
||
// newOfflineDevice 造一个 device,其 clipboardsue 必然在 clipboardCheck 阶段失败。
|
||
func newOfflineDevice(t *testing.T) *devicectlDevice {
|
||
t.Helper()
|
||
return &devicectlDevice{reg: NewRegistry()}
|
||
}
|
||
|
||
func TestClipboardsue_EmptyTextAndContentRejected(t *testing.T) {
|
||
d := newOfflineDevice(t)
|
||
_, err := d.clipboardsue(map[string]interface{}{"device_id": "x"})
|
||
if err == nil {
|
||
t.Fatal("text 与 content 都为空时应当报错(不能静默写空剪切板)")
|
||
}
|
||
if !strings.Contains(err.Error(), "text") && !strings.Contains(err.Error(), "content") {
|
||
t.Errorf("错误信息应指明缺哪个参数,实际: %v", err)
|
||
}
|
||
// 只有空白也视为空
|
||
if _, err2 := d.clipboardsue(map[string]interface{}{
|
||
"device_id": "x", "text": " ", "content": " ",
|
||
}); err2 == nil {
|
||
t.Error("空白 text/content 也应报错")
|
||
}
|
||
}
|
||
|
||
func TestClipboardsue_DeviceCheckedBeforeReadingFile(t *testing.T) {
|
||
// 设备不存在 ⇒ 必须先报「设备不存在」,
|
||
// 而不是先读文件报「文件不存在」——否则离线设备会伪装成文件问题。
|
||
d := newOfflineDevice(t)
|
||
_, err := d.clipboardsue(map[string]interface{}{
|
||
"device_id": "no-such-device",
|
||
"content": filepath.Join(t.TempDir(), "nope.txt"),
|
||
})
|
||
if err == nil {
|
||
t.Fatal("设备不存在时应当报错")
|
||
}
|
||
if !strings.Contains(err.Error(), "no-such-device") {
|
||
t.Errorf("应先报设备问题(设备 %s 不存在),实际: %v", "no-such-device", err)
|
||
}
|
||
}
|
||
|
||
func TestClipboardsueMaxBytesIsTwoMB(t *testing.T) {
|
||
// 上限与 GUI 侧 CAPABILITY_SOURCE_MAX_BYTES 对齐(2MB)。
|
||
// 改这个常量时这条判据会红——提醒同步改 GUI。
|
||
if clipboardsueMaxBytes != 2*1024*1024 {
|
||
t.Errorf("clipboardsue 上限应为 2MB(与 GUI 侧一致),实际 %d", clipboardsueMaxBytes)
|
||
}
|
||
}
|
||
|
||
func TestClipboardsue_ContentFileReadable(t *testing.T) {
|
||
// 造一个真文件,确认 os.ReadFile 能读到(判据本身的自检:
|
||
// 若将来换成别的读法或路径处理变了,这里会先炸)。
|
||
dir := t.TempDir()
|
||
p := filepath.Join(dir, "note.txt")
|
||
want := "行1\n行2\n"
|
||
if err := os.WriteFile(p, []byte(want), 0o644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
b, err := os.ReadFile(p)
|
||
if err != nil {
|
||
t.Fatalf("读取失败: %v", err)
|
||
}
|
||
if string(b) != want {
|
||
t.Errorf("读回内容不符: %q", string(b))
|
||
}
|
||
}
|
||
|
||
// TestClipboardsue_ContentOversizeRejected 钉住**上限真的被用上**。
|
||
//
|
||
// ★ 这条判据是补来的:我先前只写了「常量等于 2MB」那条,
|
||
//
|
||
// 把 `if len(b) > clipboardsueMaxBytes` 改成 `if false` 它照样绿——
|
||
// **测的是常量、不是闸**。凡是要验「闸真的关着」,
|
||
// 必须造一个越过阈值的数据去撞它,而不是断言阈值的数值。
|
||
func TestClipboardsue_ContentOversizeRejected(t *testing.T) {
|
||
// 造一个在线且已授权的设备,让 clipboardCheck 过;
|
||
// 随后超限文件必须在**读文件后的校验**处被拦下。
|
||
dir := t.TempDir()
|
||
p := filepath.Join(dir, "big.txt")
|
||
big := make([]byte, clipboardsueMaxBytes+1)
|
||
for i := range big {
|
||
big[i] = 'x'
|
||
}
|
||
if err := os.WriteFile(p, big, 0o644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
reg := NewRegistry()
|
||
// 注册一个在线设备并授 capability:clipboardsue。
|
||
reg.devices["d1"] = &DeviceMeta{
|
||
DeviceID: "d1", Name: "d", Kind: "computer", Online: true,
|
||
Caps: []string{"clipboardsue"},
|
||
}
|
||
d := &devicectlDevice{reg: reg}
|
||
|
||
_, err := d.clipboardsue(map[string]interface{}{
|
||
"device_id": "d1",
|
||
"content": p,
|
||
})
|
||
if err == nil {
|
||
t.Fatal("超 2MB 的 content 应当被拒绝(把上限改成 if false 时这条会红)")
|
||
}
|
||
if !strings.Contains(err.Error(), "过大") && !strings.Contains(err.Error(), "上限") {
|
||
t.Errorf("错误信息应说明超限,实际: %v", err)
|
||
}
|
||
}
|