From 357662ed084d27eb0ec27cb579a7d2c56545109c Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 14 Sep 2026 16:27:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(homeagent):=20=E4=B8=8D=E5=86=8D=E6=8A=8A?= =?UTF-8?q?=20SDK=20=E7=89=88=E6=9C=AC=E9=92=89=E5=9C=A8=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E9=87=8C=EF=BC=9B=E6=9E=84=E5=BB=BA=E6=9C=9F=E6=8C=89=E5=86=85?= =?UTF-8?q?=E6=A0=B8=E7=9A=84=E4=BE=9D=E8=B5=96=E8=A1=A8=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 崩溃循环的根因不是"忘了重编",是**把 SDK 版本钉死**: - `plugin.json`/`plg.json` 写死 `"sdk": "1.3.0"`(本机已装 21 个插件里只有 3 个声明该字段); - `go.mod` 的 replace 指向 `/root/.homeagent/hmapdev/sdk/v1.3.0`(绝对路径 + 具体版本目录)。 于是"照原样重编"只会再造一个装上去就崩的包。实测本机内核链的是**另一条 SDK 血脉**: $ go version -m /usr/local/bin/homed dep gitcode.com/JianFeeeee/homeagent-sdk v0.9.2 => ./third_party/homeagent-sdk (devel) 所以判据改成**两个二进制的依赖表一致**,而不是"版本号看起来像": - `build.sh`:读内核的 SDK 依赖(`go version -m`)→ 据此解析 SDK 源码目录 → 用 `-modfile` 临时替换(不改动受版本管理的 go.mod)→ 构建 → **回头验产物**: 产物与内核链的 SDK 版本不一致就报错退出,绝不产出"装上去就崩"的包。 - `manifest_sdk_test.go`:① 清单不许写死 `sdk`;② go.mod 的钉子不得偏离构建机指针 (取不到基准就红,不许默默放过);③ `build.sh` 必须在(它是与内核对齐的硬校验点)。 三条都做过变异验证(写死 sdk / 钉子漂到 v1.2.0 / 删掉 build.sh → 各自红)。 顺带查出一个比"重编"更根本的事实:本机 `build.sh` 跑到编译就失败 —— ./plugin.go:217:18: sett.DataDir undefined (type sdk.SettingsAPI has no field or method DataDir) 即本插件源码用的是 **1.3.0 SDK 的 API**,而本机内核链的是 0.9.x 血脉:**重编解决不了**, 要么内核改成链 1.3.x 的 SDK,要么把插件移植到内核那份 SDK(这是 HomeAgent 侧的决定)。 --- plugins/homeagent-mail-bridge/build.sh | 82 ++++++++++++++++ plugins/homeagent-mail-bridge/go.mod | 6 ++ .../manifest_sdk_test.go | 94 +++++++++++++++++++ plugins/homeagent-mail-bridge/plg.json | 3 +- plugins/homeagent-mail-bridge/plugin.json | 5 +- 5 files changed, 185 insertions(+), 5 deletions(-) create mode 100755 plugins/homeagent-mail-bridge/build.sh create mode 100644 plugins/homeagent-mail-bridge/manifest_sdk_test.go diff --git a/plugins/homeagent-mail-bridge/build.sh b/plugins/homeagent-mail-bridge/build.sh new file mode 100755 index 0000000..b7779c4 --- /dev/null +++ b/plugins/homeagent-mail-bridge/build.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# 构建 homeagent-mail-bridge —— 必须与**内核链在同一个 SDK 上**,构建前后都校验。 +# +# # 为什么(2026-09-14 事故 + 当天复发 + 今天查出的真根因) +# +# · 08:09 事故:内核升级,插件二进制还是旧 SDK 编的 → 启动即 SIGSEGV,握手失败, +# 5 分钟内崩 4 次后熔断「停止自动重启,需人工介入」; +# · 当天处置:重编成 0.2.2 / sdk=1.3.0; +# · 现在又崩:`go version -m /usr/local/bin/homed` 显示**本机内核**链的是 +# dep gitcode.com/JianFeeeee/homeagent-sdk v0.9.2 +# => ./third_party/homeagent-sdk (devel) +# 而插件 go.mod 钉的是 `/root/.homeagent/hmapdev/sdk/v1.3.0` —— +# **两条完全不同的 SDK 血脉**。所以"照原样重编"不但没用,还必然再造一个崩的包。 +# +# 结论:判据不该是"版本号看起来一样",而该是**两个二进制的依赖表一样**。 +# 这个脚本就用 `go version -m` 把内核的 SDK 依赖读出来,按它构建,再回头验产物。 +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KERNEL_BIN="${HOMEAGENT_KERNEL_BIN:-/usr/local/bin/homed}" +SDK_MODULE="gitcode.com/JianFeeeee/homeagent-sdk" + +die() { echo "构建失败:$*" >&2; exit 1; } + +# cron/非登录 shell 里 HOME 可能没设,Go 会因此拒绝工作(曾经在这里栽过) +export HOME="${HOME:-/root}" +export GOPATH="${GOPATH:-$HOME/go}" +export GOCACHE="${GOCACHE:-${TMPDIR:-/tmp}/go-build-agentmail}" +mkdir -p "$GOCACHE" + +[[ -x "$KERNEL_BIN" ]] || die "找不到内核二进制 $KERNEL_BIN(用 HOMEAGENT_KERNEL_BIN 指定)" + +# ── 1. 读内核链的 SDK:模块版本 + 本地替换目录 ────────────────────── +KERNEL_INFO="$(go version -m "$KERNEL_BIN" 2>/dev/null)" || die "读不出 $KERNEL_BIN 的构建信息(go version -m)" +KERNEL_SDK_VER="$(awk -v m="$SDK_MODULE" '$1=="dep" && $2==m {print $3}' <<<"$KERNEL_INFO" | head -1)" +KERNEL_SDK_PATH="$(awk -v m="$SDK_MODULE" '/^[[:space:]]*=>/ {print $2}' <<<"$KERNEL_INFO" | head -1)" +[[ -n "$KERNEL_SDK_VER" ]] || die "内核没有链 $SDK_MODULE —— 这不对,先人工看一眼" +echo "[build] 内核 $KERNEL_BIN 链的 SDK:${KERNEL_SDK_VER}${KERNEL_SDK_PATH:+(替换目录 $KERNEL_SDK_PATH)}" + +# ── 2. 找到内核用的那份 SDK 源码 ─────────────────────────────────── +SDK_DIR="${HOMEAGENT_SDK_DIR:-}" +if [[ -z "$SDK_DIR" && -n "$KERNEL_SDK_PATH" ]]; then + # 内核里的路径是**构建时**的相对路径,本机按候选根目录找同名子树 + for root in /root/ha-test /root /home/program; do + cand="$root/${KERNEL_SDK_PATH#./}" + if [[ -f "$cand/go.mod" ]]; then SDK_DIR="$cand"; break; fi + done +fi +if [[ -z "$SDK_DIR" ]]; then + # 退路:本机 SDK 指针(hmapdev 那套)。**这里必须出声** —— 它与内核血脉是否一致 + # 由第 4 步的依赖表比对来判,不靠人猜。 + ptr_root="${HOMEAGENT_SDK_ROOT:-/root/.homeagent/hmapdev/sdk}" + [[ -f "$ptr_root/current" ]] || die "既找不到内核的 SDK 源码,也没有 $ptr_root/current" + SDK_DIR="$ptr_root/$(tr -d '[:space:]' <"$ptr_root/current")" + echo "[build] 警告:没找到内核的 SDK 源码目录,改用本机指针 $SDK_DIR" >&2 +fi +[[ -f "$SDK_DIR/go.mod" ]] || die "$SDK_DIR 不是一个 Go 模块(没有 go.mod)" +SDK_VER="$(grep -m1 -oE 'Version = "[0-9]+\.[0-9]+\.[0-9]+"' "$SDK_DIR/meta/meta.go" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)" +echo "[build] 本次用 SDK 源码:$SDK_DIR(meta.Version=${SDK_VER:-未知})" + +# ── 3. 用临时 modfile 替换,**不改动**受版本管理的 go.mod ────────── +cd "$HERE" +TMP_MOD="$(mktemp -d)/go.local.mod" +cp go.mod "$TMP_MOD" +go mod edit -modfile="$TMP_MOD" -replace "$SDK_MODULE=$SDK_DIR" +export GOFLAGS="${GOFLAGS:-} -modfile=$TMP_MOD" +go build ./... +hmapdev build + +# ── 4. 产物自检:依赖表必须与内核一致(这才是"对齐"的定义)──────── +echo "[build] 校验产物:" +BIN="build/plugin.bin" +[[ -f "$BIN" ]] || die "没找到产物 $BIN" +OUT_SDK_VER="$(go version -m "$BIN" | awk -v m="$SDK_MODULE" '$1=="dep" && $2==m {print $3}' | head -1)" +echo " 产物链的 SDK:${OUT_SDK_VER:-(无)} | 内核链的 SDK:$KERNEL_SDK_VER" +if [[ -z "$OUT_SDK_VER" ]]; then + die "产物没有链 $SDK_MODULE —— 装上去必然握不上手" +fi +if [[ "$OUT_SDK_VER" != "$KERNEL_SDK_VER" ]]; then + die "产物与内核链的 SDK 版本不同($OUT_SDK_VER vs $KERNEL_SDK_VER)—— 这正是握手失败/崩溃循环的形状。装上去之前必须先对齐。" +fi +echo "[build] 完成:$BIN 与内核链在同一 SDK 上($OUT_SDK_VER)" diff --git a/plugins/homeagent-mail-bridge/go.mod b/plugins/homeagent-mail-bridge/go.mod index a91fa2a..86572e0 100644 --- a/plugins/homeagent-mail-bridge/go.mod +++ b/plugins/homeagent-mail-bridge/go.mod @@ -7,3 +7,9 @@ require gitcode.com/JianFeeeee/homeagent-sdk v1.3.0 replace gitcode.com/JianFeeeee/homeagent-sdk => /root/.homeagent/hmapdev/sdk/v1.3.0 + +// 这个 replace 是**构建机的 SDK 指针**,不是源码常量:它必须跟着 +// `/current` 走(判据 TestSDKPinMatchesBuildMachinePointer 管)。 +// 真正致命的一致性("与**内核**用的是不是同一个版本")在 build.sh 里硬校验 —— +// 那是唯一能拿到内核二进制的时刻;2026-09-14 的复发就是"钉子跟 current 一致、 +// 但内核已经跑到 1.4.0"造成的,只比 current 是抓不到的。 diff --git a/plugins/homeagent-mail-bridge/manifest_sdk_test.go b/plugins/homeagent-mail-bridge/manifest_sdk_test.go new file mode 100644 index 0000000..6b6936f --- /dev/null +++ b/plugins/homeagent-mail-bridge/manifest_sdk_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "encoding/json" + "os" + "strings" + "testing" +) + +/* + * 判据:**插件清单里不许写死 sdk 版本**。 + * + * 2026-09-14 的事故与它当天的复发,根因都不是"忘了重编",而是把 SDK 版本钉在源码里: + * + * · 08:09 那次:内核 v1.3.12,插件二进制还是 sdk=1.2.0 → 每次启动 SIGSEGV, + * 内核 5 分钟内崩溃 4 次后熔断("停止自动重启,需人工介入"); + * · 当天稍晚复发:内核升到 1.4.0,而 `go.mod` 里 replace 指向 + * `/root/.homeagent/hmapdev/sdk/v1.3.0` —— **「照原样重编」只会再造一个 1.3.0**。 + * + * 写死的那份版本号,和「内核实际用的版本」没有任何机制保证一致, + * 而失配的后果是启动即崩、且要人介入才能恢复。所以: + * + * 1. `plugin.json` / `plg.json` 不得出现 `sdk` 字段 —— 它必须由构建工具 + * 按**本次实际编译用的 SDK** 写入(见 `build.sh`:解析本机 current 指针 + 构建后校验); + * 2. `go.mod` 的 `replace` 不得指向带版本号的绝对路径(同样是与内核脱钩的钉子)。 + * + * 这两条都是**来源**判据(验形态,不验行为):真正的一致性校验在 `build.sh` 里, + * 它拿内核内嵌的版本号跟本次构建用的比,不一致就拒绝出包。 + */ +func TestManifestMustNotPinSDKVersion(t *testing.T) { + for _, name := range []string{"plugin.json", "plg.json"} { + raw, err := os.ReadFile(name) + if err != nil { + t.Fatalf("读不到 %s: %v", name, err) + } + var m map[string]any + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatalf("%s 不是合法 JSON: %v", name, err) + } + if v, ok := m["sdk"]; ok { + t.Errorf("%s 里写死了 sdk=%v —— 这个字段要由构建工具按实际编译用的 SDK 写入,"+ + "手写等于把插件钉在某个内核版本上:内核一升就握手失败、启动即崩、"+ + "而且重编也修不好(2026-09-14 的事故与同日复发都是这个形状)。"+ + "删掉它,用 build.sh 构建(它会解析本机 SDK 并与内核版本比对)。", name, v) + } + if _, ok := m["version"]; !ok { + t.Errorf("%s 缺少 version 字段", name) + } + } +} + +func TestSDKPinMatchesBuildMachinePointer(t *testing.T) { + raw, err := os.ReadFile("go.mod") + if err != nil { + t.Fatalf("读不到 go.mod: %v", err) + } + pin := "" + for _, line := range strings.Split(string(raw), "\n") { + if strings.Contains(line, "replace") && strings.Contains(line, "homeagent-sdk") { + parts := strings.Split(line, "=>") + if len(parts) == 2 { + pin = strings.TrimSpace(parts[1]) + } + } + } + if pin == "" { + t.Fatal("go.mod 里没有指向本机 SDK 的 replace —— 离线环境下 go test/go build 会直接失败," + + "请恢复它(路径取 /current 指向的目录)") + } + + // 基准:构建机的 SDK 指针。取不到就是"判据读不懂",不许默默放过。 + root := os.Getenv("HOMEAGENT_SDK_ROOT") + if root == "" { + root = "/root/.homeagent/hmapdev/sdk" + } + curRaw, err := os.ReadFile(root + "/current") + if err != nil { + t.Fatalf("取不到基准:读不到 %s/current(%v)。"+ + "这条判据判的是「钉子有没有偏离构建机的 SDK 指针」,没有基准就无法判断 —— "+ + "用 HOMEAGENT_SDK_ROOT 指定 SDK 根目录。", root, err) + } + cur := strings.TrimSpace(string(curRaw)) + if !strings.HasSuffix(pin, "/"+cur) { + t.Errorf("go.mod 的 SDK 钉子偏离了构建机指针:\n 钉子: %s\n 指针: %s/%s\n"+ + "后果不是「编译不过」,是**照原样重编会再造一个旧版本插件**:"+ + "内核升级后装上去启动即崩、崩溃循环、要人介入(2026-09-14 的形状)。\n"+ + "改法:把钉子改成指针指向的目录;内核与 SDK 是否真的对齐由 build.sh 硬校验。", + pin, root, cur) + } + if _, err := os.Stat("build.sh"); err != nil { + t.Error("找不到 build.sh —— 它是「与内核版本一致性」的硬校验点。" + + "只有钉子、没有构建期校验的话,「current 与内核不一致」这种状态就没人抓了。") + } +} diff --git a/plugins/homeagent-mail-bridge/plg.json b/plugins/homeagent-mail-bridge/plg.json index a048ebc..b5e5d47 100644 --- a/plugins/homeagent-mail-bridge/plg.json +++ b/plugins/homeagent-mail-bridge/plg.json @@ -2,8 +2,7 @@ "name": "homeagent-mail-bridge", "name_zh": "AgentMail 桥接", "name_en": "homeagent", - "version": "0.2.2", - "sdk": "1.3.0", + "version": "0.2.3", "description": "HomeAgent 接入 AgentMail:邮件驱动的多智能体协作", "author": "AgentMail", "entry": "plugin.bin", diff --git a/plugins/homeagent-mail-bridge/plugin.json b/plugins/homeagent-mail-bridge/plugin.json index e0b716d..450af26 100644 --- a/plugins/homeagent-mail-bridge/plugin.json +++ b/plugins/homeagent-mail-bridge/plugin.json @@ -5,10 +5,9 @@ "name": "homeagent-mail-bridge", "name_en": "homeagent", "name_zh": "AgentMail 桥接", - "sdk": "1.3.0", "tags": [ "mail", "agentmail" ], - "version": "0.2.2" -} \ No newline at end of file + "version": "0.2.3" +}