diff --git a/deploy/packaging/build.sh b/deploy/packaging/build.sh index 0ca37d1..c3329e4 100644 --- a/deploy/packaging/build.sh +++ b/deploy/packaging/build.sh @@ -1,12 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# 本脚本位于 deploy/packaging/,故仓库根在上两级。 +# +# v0.7.2 的根目录清理把 package/build.sh 移到 deploy/packaging/build.sh +# (深度 1 → 2 层),但这行的 ".." 没跟着改成 "../..",于是 PROJECT_ROOT +# 变成了 /deploy:产物落进 deploy/build/、GUI 去找 deploy/cmd/gui。 +# 跨平台构建从那次起一直是坏的(Makefile 的单平台 build 不走这里,所以没暴露)。 +PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" BUILD_DIR="${PROJECT_ROOT}/build" VERSION="${VERSION:-$(git -C "$PROJECT_ROOT" describe --tags --dirty 2>/dev/null || echo "0.8.0")}" COMMIT="${COMMIT:-$(git -C "$PROJECT_ROOT" rev-parse --short HEAD 2>/dev/null || echo "unknown")}" BUILD_TIME="${BUILD_TIME:-$(date -u '+%Y-%m-%dT%H:%M:%SZ')}" -GO="${GO:-$(command -v go 2>/dev/null || echo "/home/jianf/go1.26.5/go/bin/go")}" +GO="${GO:-$(command -v go 2>/dev/null || echo "go")}" LDFLAGS="-X gitcode.com/JianFeeeee/HomeAgent/internal/meta.Version=${VERSION} -X gitcode.com/JianFeeeee/HomeAgent/internal/meta.Commit=${COMMIT} -X gitcode.com/JianFeeeee/HomeAgent/internal/meta.BuildTime=${BUILD_TIME}" TARGET="${1:-native}" @@ -21,10 +27,10 @@ COMPONENT="${2:-all}" case "$TARGET" in native) GOOS="" GOARCH="" ;; linux/amd64) GOOS=linux GOARCH=amd64 CC="${CC:-}" ;; - linux/arm64) GOOS=linux GOARCH=arm64 CC="${CC:-aarch64-linux-gnu-gcc}" CXX="${CXX:-aarch64-linux-gnu-g++}" ;; + linux/arm64) GOOS=linux GOARCH=arm64 CC="${CC:-aarch64-linux-gnu-gcc}" ;; darwin/amd64) GOOS=darwin GOARCH=amd64 CC="${CC:-}" ;; darwin/arm64) GOOS=darwin GOARCH=arm64 CC="${CC:-}" ;; - windows/amd64) GOOS=windows GOARCH=amd64 CC="${CC:-x86_64-w64-mingw32-gcc}" CXX="${CXX:-x86_64-w64-mingw32-g++}" ;; + windows/amd64) GOOS=windows GOARCH=amd64 CC="${CC:-x86_64-w64-mingw32-gcc}" ;; all) "$0" linux/amd64 "$COMPONENT" "$0" linux/arm64 "$COMPONENT" @@ -36,7 +42,7 @@ case "$TARGET" in *) echo "Unknown target: $TARGET" echo "Usage: $0 [native|linux/amd64|linux/arm64|darwin/amd64|darwin/arm64|windows/amd64|all]" - echo " [all|homed|waiter|gui]" + echo " [all|homed|waiter|initconfig|gui]" exit 1 esac @@ -67,7 +73,11 @@ build_homed() { out="${out}.exe" fi echo "[BUILD] homed ${plat} → $out" - CGO_ENABLED=1 "$GO" build -trimpath -installsuffix dynlink \ + # Go 用 CC 驱动 CGO 编译与链接,用 CC 指定的交叉工具链来决定目标架构。 + # 必须同时 export CC 给 Go 的 CGO 代码生成器,否则 CGO_ENABLED=1 下的 + # 目标文件与 host 的 ld 不兼容(如 arm64 的 .o 给了 x86_64 的 ld)。 + local _cc="${CC:-cc}" + CGO_ENABLED=1 CC="$_cc" "$GO" build -trimpath -installsuffix dynlink \ -ldflags "$LDFLAGS" -o "$out" ./cmd/homed/ echo " OK ($(file "$out" | sed 's/.*: //') | $(du -h "$out" | cut -f1))" } @@ -84,6 +94,22 @@ build_waiter() { echo " OK ($(du -h "$out" | cut -f1))" } +# ---- initconfig (CGO-free 配置初始化器) ---- +# +# NSIS 安装包(installer.nsi:220 File "..\build\initconfig.exe")与 +# package-linux.sh 的 stage_variant 都引用它,但此前 build.sh 从不构建它—— +# Windows 安装包构建会直接失败在缺文件上。 +build_initconfig() { + local plat="${GOOS:-linux}/${GOARCH:-amd64}" + local out="$BUILD_DIR/initconfig${SUFFIX:+_$SUFFIX}" + if [ "$GOOS" = "windows" ]; then out="${out}.exe"; fi + + echo "[BUILD] initconfig ${plat} → $out" + CGO_ENABLED=0 "$GO" build -trimpath -installsuffix dynlink \ + -ldflags "$LDFLAGS" -o "$out" ./cmd/initconfig/ + echo " OK ($(du -h "$out" | cut -f1))" +} + # ---- gui (Electron) ---- build_gui() { if [ -n "${GOOS:-}" ] && [ "$GOOS" != "$("$GO" env GOOS)" ]; then @@ -99,7 +125,10 @@ build_gui() { (cd "$gui_dir" && npm install --production) fi - (cd "$gui_dir" && npx electron-builder --config "$gui_dir/package.json" \ + # 不传 --config:electron-builder 默认从 package.json 的 "build" 键读配置。 + # 传 --config package.json 会让它把**整个** package.json 当配置校验, + # 于是 devDependencies / build / scripts 全被判为 "unknown property" 而失败。 + (cd "$gui_dir" && npx electron-builder \ --linux --win --mac \ --x64 --arm64 \ -p never \ @@ -109,9 +138,10 @@ build_gui() { # ---- dispatch ---- case "$COMPONENT" in - all) build_homed; build_waiter; build_gui ;; + all) build_homed; build_waiter; build_initconfig; build_gui ;; homed) build_homed ;; waiter) build_waiter ;; + initconfig) build_initconfig ;; gui) build_gui ;; *) echo "Unknown component: $COMPONENT" diff --git a/deploy/packaging/package-linux.sh b/deploy/packaging/package-linux.sh index 547bf41..d508df6 100644 --- a/deploy/packaging/package-linux.sh +++ b/deploy/packaging/package-linux.sh @@ -6,7 +6,7 @@ BUILD_DIR="${PROJECT_ROOT}/build" DIST_DIR="${PROJECT_ROOT}/dist/linux" VERSION="${VERSION:-$(git -C "$PROJECT_ROOT" describe --tags --dirty 2>/dev/null || echo "0.8.0")}" PACKAGE_ROOT="${PROJECT_ROOT}/deploy/packaging/linux" -GO="${GO:-$(command -v go 2>/dev/null || echo "/home/jianf/go1.26.5/go/bin/go")}" +GO="${GO:-$(command -v go 2>/dev/null || echo "go")}" ARCH="${1:-amd64}" # amd64 or arm64 ACTION="${2:-all}" # all, build, deb, tar, rpm @@ -99,6 +99,9 @@ build_go() { bash "$PROJECT_ROOT/deploy/packaging/build.sh" "linux/$ARCH" "waiter" 2>&1 || { echo "WARNING: waiter build failed." } + bash "$PROJECT_ROOT/deploy/packaging/build.sh" "linux/$ARCH" "initconfig" 2>&1 || { + echo "WARNING: initconfig build failed(包内将缺少首次配置初始化器)。" + } local suffix="linux_${ARCH}" local homed_bin="$BUILD_DIR/homed_$suffix" diff --git a/internal/agent/core/status.go b/internal/agent/core/status.go index c67f2f9..46703e6 100644 --- a/internal/agent/core/status.go +++ b/internal/agent/core/status.go @@ -10,6 +10,7 @@ import ( "gitcode.com/JianFeeeee/HomeAgent/internal/memory/document" "gitcode.com/JianFeeeee/HomeAgent/internal/memory/social" "gitcode.com/JianFeeeee/HomeAgent/internal/memory/text" + "gitcode.com/JianFeeeee/HomeAgent/internal/meta" "gitcode.com/JianFeeeee/HomeAgent/internal/plugin" sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" "gitcode.com/JianFeeeee/HomeAgent/internal/tracker" @@ -33,6 +34,7 @@ type LLMStatus = sdk.LLMStatus type ContextStatus = sdk.ContextStatus type RuntimeStatus = sdk.RuntimeStatus type TrackerStatus = sdk.TrackerStatus +type BuildStatus = sdk.BuildStatus func channelInfoFromIO(ch agentIO.ChannelInfo) ChannelInfo { return ChannelInfo{ @@ -63,6 +65,15 @@ func collectKernelStatus( Uptime: time.Since(startTime).Round(time.Second).String(), StartTime: startTime.Format(time.RFC3339), AgentID: agentID, + // 构建身份取自内核自己的 meta(-ldflags 注入点), + // 而非 SDK 仓的硬编码版本。 + Build: BuildStatus{ + Version: meta.Version, + Commit: meta.Commit, + BuildTime: meta.BuildTime, + SDKCompatible: meta.SDKCompatibleVersion, + KernelName: meta.KernelName, + }, Runtime: RuntimeStatus{ Goroutines: runtime.NumGoroutine(), GoVersion: runtime.Version(), diff --git a/internal/plugin/dynamic_proc_unix.go b/internal/plugin/dynamic_proc.go similarity index 90% rename from internal/plugin/dynamic_proc_unix.go rename to internal/plugin/dynamic_proc.go index 20ed3f1..23ff7cd 100644 --- a/internal/plugin/dynamic_proc_unix.go +++ b/internal/plugin/dynamic_proc.go @@ -1,5 +1,3 @@ -//go:build linux || darwin - package plugin import ( @@ -14,6 +12,20 @@ import ( sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" ) +// 本文件平台中立。 +// +// 曾经拆成 dynamic_proc_unix.go + dynamic_proc_windows.go(Part 1,610e9d0), +// 当时共享内存只有 POSIX mmap 实现,故 Windows 侧只放了个报「尚未实现」的桩。 +// 但那个桩只定义了 tryLoadProc,而平台中立的 registry.go 还在调 loadProc / +// closeProcHost —— **Windows 下整个 homed 从那时起就编译不过** +// (plan.md §12.5 声称「交叉编译通过」,实际只验证了 proc 子包)。 +// +// Part 6.2(d027c96)补齐了 Windows 共享内存(CreateFileMappingW)、事件通知 +// (CreateEventW)与段传递(命名对象经环境变量),桩却没人回头删。 +// +// 现在合回一个文件:本文件里没有任何平台专属调用,全部差异封装在 proc 包的 +// shmalloc_* / evtfd_* / shmpass_* / procattr_* 里,那些文件各自带构建标签。 + // tryLoadProc 只做静态校验(供双通道探测与测试),不构造插件实体。 // // 真正加载走 Registry.loadProc:子进程插件需要共享段 Host, diff --git a/internal/plugin/dynamic_proc_windows.go b/internal/plugin/dynamic_proc_windows.go deleted file mode 100644 index 0942ae1..0000000 --- a/internal/plugin/dynamic_proc_windows.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build windows - -package plugin - -import ( - "fmt" - "os" - "path/filepath" - - sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" -) - -// tryLoadProc 的 Windows 桩:子进程通道本身是跨平台的(stdio JSON-RPC 无平台差异), -// 但共享内存数据面当前基于 POSIX mmap,Windows 需改用 CreateFileMapping。 -// -// 迁移评估 §9.2 已记录:Windows DLL 路径当前能力严重退化(只下发 3 字段、无写回), -// 迁移到子进程后三套 ABI 收敛为单一 RPC 实现,Windows 反而受益——但需要测试机验证。 -func tryLoadProc(dir, name string, config map[string]interface{}) (sdk.Plugin, error) { - for _, candidate := range []string{binEntry, "plugin.exe"} { - if st, err := os.Stat(filepath.Join(dir, candidate)); err == nil && !st.IsDir() { - return nil, fmt.Errorf("proc plugin %s: Windows 子进程通道尚未实现(Part 2 + §9.2)", name) - } - } - return nil, nil -} diff --git a/internal/plugins/webui/dashboard.html b/internal/plugins/webui/dashboard.html index e6efb0e..d4b5e2c 100644 --- a/internal/plugins/webui/dashboard.html +++ b/internal/plugins/webui/dashboard.html @@ -2669,6 +2669,24 @@ applyI18n(); } + // fmtBuild 把构建身份压成一行:commit + 构建时间(日期部分)。 + // + // 未注入 ldflags 的本地构建会得到 "unknown",此时只显示 SDK 版本, + // 避免把 "unknown / unknown" 这种噪音摆在概览卡上。 + function fmtBuild(b, s) { + var parts = []; + var commit = (b && b.commit) || (s && s.commit) || ""; + if (commit && commit !== "unknown") parts.push(commit); + var bt = (b && b.build_time) || (s && s.build_time) || ""; + if (bt && bt !== "unknown") { + // 2026-09-03T05:55:35Z → 2026-09-03 + parts.push(String(bt).split("T")[0]); + } + var sdkv = (b && b.sdk_compatible) || (s && s.sdk_version) || ""; + if (sdkv) parts.push("SDK " + sdkv); + return parts.length ? parts.join(" · ") : "-"; + } + function fmtUptime(ms) { var s = Math.floor(ms / 1000); if (s < 60) return s + "s"; @@ -2724,7 +2742,18 @@ '
' + __("版本", "Version") + '' + - escHtml(s.version || "0.1.0") + + escHtml( + k?.build?.version + ? (k.build.kernel_name || "HomeAgent") + " v" + k.build.version + : s.version + ? "v" + s.version + : "-", + ) + + "
" + + '
' + + __("构建", "Build") + + '' + + escHtml(fmtBuild(k?.build, s)) + "
"; if (k) { html += diff --git a/internal/plugins/webui/handler.go b/internal/plugins/webui/handler.go index a08f7ce..1d47676 100644 --- a/internal/plugins/webui/handler.go +++ b/internal/plugins/webui/handler.go @@ -22,6 +22,7 @@ import ( "time" agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io" + "gitcode.com/JianFeeeee/HomeAgent/internal/meta" sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" "gitcode.com/JianFeeeee/HomeAgent/pkg/types" ) @@ -943,11 +944,20 @@ func (h *Handler) handleStatus(w http.ResponseWriter, r *http.Request) { agentCount = len(h.supervisor.ListAgents()) } writeJSON(w, http.StatusOK, map[string]interface{}{ - "status": "running", - "uptime": time.Since(h.startTime).Round(time.Second).String(), - "agents": agentCount, - "version": sdk.SDKVersion, - "startedAt": h.startTime, + "status": "running", + "uptime": time.Since(h.startTime).Round(time.Second).String(), + "agents": agentCount, + // 内核版本取 internal/meta(-ldflags 注入点)。 + // + // 此前这里给的是 `sdk.SDKVersion`,那条链最终指向 **SDK 仓 + // meta.Version 的硬编码值**,与构建时注入的内核版本无关—— + // 两仲版本号碰巧相等时看不出问题,一旦不等就报错。 + // SDK 版本另用 sdk_version 字段并行暴露。 + "version": meta.Version, + "commit": meta.Commit, + "build_time": meta.BuildTime, + "sdk_version": sdk.SDKVersion, + "startedAt": h.startTime, }) } diff --git a/internal/sdk/status.go b/internal/sdk/status.go index 5d5b2b7..19e5bd3 100644 --- a/internal/sdk/status.go +++ b/internal/sdk/status.go @@ -10,6 +10,16 @@ type KernelStatus struct { Uptime string `json:"uptime"` StartTime string `json:"start_time"` + // Build 是内核自身的版本与构建信息。 + // + // 为何需要:此前 WebUI 展示的“版本”取自 `sdk.SDKVersion`,那是 **SDK 仓 + // meta.Version 的硬编码值**;而 `-ldflags` 注入的是内核的 + // `internal/meta.Version`——两条链完全不相交。于是: + // - 构建时注入的真实版本号、commit、构建时间全部丢失; + // - 两仲版本号碰巧相等时看不出问题,一旦不等就报错版本; + // - 无法回答“现网跑的是哪个 commit 构出来的”。 + Build BuildStatus `json:"build"` + AgentID string `json:"agent_id"` Plugins []PluginInfo `json:"plugins"` @@ -87,6 +97,22 @@ type RuntimeStatus struct { GoVersion string `json:"go_version"` } +// BuildStatus 是内核二进制的构建身份,全部来自 `internal/meta` +// (由 -ldflags 在构建时注入,未注入时为源码默认值/unknown)。 +type BuildStatus struct { + // Version 是内核语义版本(如 1.0.0)。 + Version string `json:"version"` + // Commit 是构建时的 Git 短 hash;未注入为 unknown。 + Commit string `json:"commit"` + // BuildTime 是 UTC 构建时间;未注入为 unknown。 + BuildTime string `json:"build_time"` + // SDKCompatible 是本内核可兼容的最高 SDK 版本。 + // 插件用旧 SDK 编译时靠它判断能不能加载。 + SDKCompatible string `json:"sdk_compatible"` + // KernelName 是内核名(HomeAgent)。 + KernelName string `json:"kernel_name"` +} + type TrackerStatus struct { Available bool `json:"available"` Dir string `json:"dir,omitempty"`