mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
meta: 内核版本升到 1.0.0;生产切换脚本改走 hmap 正规通道(Part 6.5)
## 版本号
1.0.0:外部插件从 C ABI 动态库迁到子进程 + 共享内存。首个不再加载
`.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件必须用新版 plugindev 重编)。
SDKCompatibleVersion 同步升 1.0.0。
同时删掉 ABIVersion / CABINum / 51 个 Core<Method> 整数 ID —— 随 Part 6.2
删 internal/plugin/cabi/ 就已无使用者(grep 确认只剩定义处)。留着会让人
以为 C 层协商还在生效,或以为加 method 要同步维护那张整数表。
⚠️ 注意 Makefile 的 `VERSION ?= $(shell git describe --tags --dirty)`:
实际注入值来自 git tag,meta.go 里的默认值只在不带 ldflags 时生效。
make build 当前注入 v0.9.1-56-g2572688-dirty。要让 1.0.0 真正生效需打
v1.0.0 tag 或显式传 VERSION=1.0.0。
## 生产切换脚本重写
第一版是手工拷 plugin.bin + 手改 plugin.json 的 entry —— 那等于**重新实现
了一遍 hmap 解包逻辑,且实现得更差**。漏掉的东西:
platforms 字段 hmap 内的 plugin.json 本来就写对了
平台二进制选择 我硬编码 _linux_amd64,正规路径用 platformBinary()
overwrite 语义 StopAndUnload 停旧实例但**保留配置表**
失败回滚 os.Rename 备份旧目录,解包失败自动恢复
校验 validatePackage 查 manifest + 各平台二进制齐全
配置保留那条尤其关键:生产 17 个插件都有配置(qq 账号、weather 默认城市、
browser profile 路径)。我的脚本恰好没碰配置表所以侥幸不丢,但那是运气
不是设计。
改为 POST 到 pluginmgr 的 HTTP 端点(127.0.0.1:9876/plugins),
传 {path, overwrite:true} 走 installFromPath → installFromData。
保留的一个设计:**先全部校验再动手**。任一插件缺 hmap 就整批中止——
新 homed 不认 .so,「一半装了一半没装」的中间态最难排查。
## 生产切换已执行
顺序(先换二进制再装包,而非反过来):
1. systemctl stop homeagent
2. 换 /usr/local/bin/homed
3. 起服务 —— 15 个 .so 插件报可操作错误被跳过,homed 本体与 16 个内置正常
4. 逐个 POST 装 17 个 hmap(overwrite=true)
5. 待重启核对
第 3 步顺带在真实二进制上验证了 Part 6.2 的可操作错误:
[plugin] dynamic weather: 检测到旧 C ABI 产物(plugin.so/.dll/.dylib)。
外部插件已改为子进程模式,请用新版 plugindev 重编产出 plugin.bin
(业务代码无需修改)
不崩溃,只跳过。若反过来先装包,旧 homed 的 StopAndUnload 会停掉 qq
消息通道且无法重载 .bin,会卡在「插件全挂」的状态。
结果:17/17 成功,全部 config_kept=true;0 个残留 .so;17 个 plugin.bin
均有执行位;17 个 manifest 的 entry 均为 plugin.bin;无 .bak 残留。
bundle 包正确挑了当前平台(weather 目录只留 8.7MB 的 linux/amd64 那份)。
备份:/home/newqqagent-migration-backup-20260902-214812
(plugins 全目录 + homed.old + homeagent.service,162MB)
验证:go build ./... 通过;go test ./... 全仓无失败。
Ref: docs/zh/plugin-migration-plan.md Part 6.5
This commit is contained in:
132
docs/zh/experiments/plugin-arch/19-migration-verify/switch-production.py
Executable file
132
docs/zh/experiments/plugin-arch/19-migration-verify/switch-production.py
Executable file
@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生产切换:经 pluginmgr 正规通道安装 17 个 hmap(Part 6.5)。
|
||||
|
||||
与手工拷贝方案的区别 —— 这里复用内核自己的安装逻辑:
|
||||
|
||||
validatePackage 校验 manifest + 平台二进制齐全
|
||||
StopAndUnload 停旧实例但**保留配置表**
|
||||
os.Rename 备份 解包失败自动回滚到旧版本
|
||||
platformBinary() 按 runtime 挑当前平台那份,重命名为 plugin.bin
|
||||
chmod 0755 补执行位
|
||||
|
||||
手工拷贝会重新实现这一套,且必然实现得更差(第一版就漏了 platforms 字段
|
||||
与配置保留语义)。
|
||||
|
||||
用法:
|
||||
switch-production.py 演练
|
||||
switch-production.py --apply 实际安装
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
PROD_PLUGINS = "/home/newqqagent/plugins"
|
||||
SDK_EXAMPLE = "/home/program/TrueAgent/third_party/homeagent-sdk/example"
|
||||
PLUGINMGR = "http://127.0.0.1:9876/plugins"
|
||||
|
||||
|
||||
def find_hmap(name):
|
||||
"""找插件的 hmap 包。
|
||||
|
||||
bundle:true -> <snake>_bundle.hmap(含多平台二进制)
|
||||
bundle:false -> <snake>_<goos>_<goarch>.hmap(qq 是这种)
|
||||
"""
|
||||
dist = os.path.join(SDK_EXAMPLE, name, "dist")
|
||||
if not os.path.isdir(dist):
|
||||
return None
|
||||
cands = [f for f in os.listdir(dist) if f.endswith(".hmap")]
|
||||
if not cands:
|
||||
return None
|
||||
for c in cands:
|
||||
if c.endswith("_bundle.hmap"):
|
||||
return os.path.join(dist, c)
|
||||
return os.path.join(dist, sorted(cands)[0])
|
||||
|
||||
|
||||
def install(path):
|
||||
"""POST 到 pluginmgr。overwrite=true 走原地更新分支,保留配置表。"""
|
||||
body = json.dumps({"path": path, "overwrite": True}).encode()
|
||||
req = urllib.request.Request(
|
||||
PLUGINMGR, data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=180) as resp:
|
||||
return json.loads(resp.read().decode()), None
|
||||
except urllib.error.HTTPError as e:
|
||||
return None, "HTTP %d: %s" % (e.code, e.read().decode()[:300])
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
|
||||
|
||||
def main():
|
||||
apply = "--apply" in sys.argv
|
||||
|
||||
targets = sorted(
|
||||
d for d in os.listdir(PROD_PLUGINS)
|
||||
if os.path.isfile(os.path.join(PROD_PLUGINS, d, "plugin.so"))
|
||||
or os.path.isfile(os.path.join(PROD_PLUGINS, d, "plugin.bin"))
|
||||
)
|
||||
print("生产外部插件: %d 个" % len(targets))
|
||||
|
||||
# 先全部校验,任一缺包就整批中止。
|
||||
# 理由:新 homed 不认 .so,「一半装了一半没装」的中间态最难排查。
|
||||
plan = []
|
||||
missing = []
|
||||
for name in targets:
|
||||
h = find_hmap(name)
|
||||
if h is None:
|
||||
missing.append(name)
|
||||
else:
|
||||
plan.append((name, h))
|
||||
|
||||
if missing:
|
||||
print("\n✗ 中止:以下插件缺 hmap 包:")
|
||||
for m in missing:
|
||||
print(" " + m)
|
||||
print("\n先跑 rebuild-plugins.sh 重编。")
|
||||
return 1
|
||||
|
||||
print("✓ 全部 %d 个 hmap 就位\n" % len(plan))
|
||||
for name, h in plan:
|
||||
print(" %-16s %-44s %6d KB" % (
|
||||
name, os.path.basename(h), os.path.getsize(h) // 1024))
|
||||
|
||||
if not apply:
|
||||
print("\n[演练] 加 --apply 才实际安装")
|
||||
return 0
|
||||
|
||||
print("\n经 pluginmgr 安装(overwrite=true,保留配置)...")
|
||||
ok = 0
|
||||
failed = []
|
||||
for name, h in plan:
|
||||
result, err = install(h)
|
||||
if err:
|
||||
print(" ✗ %-16s %s" % (name, err))
|
||||
failed.append(name)
|
||||
continue
|
||||
if "error" in result:
|
||||
print(" ✗ %-16s %s: %s" % (
|
||||
name, result["error"], result.get("details", "")))
|
||||
failed.append(name)
|
||||
continue
|
||||
print(" ✓ %-16s %-12s v%s -> v%s config_kept=%s" % (
|
||||
name,
|
||||
result.get("action", "?"),
|
||||
result.get("previous_version", "?"),
|
||||
result.get("version", "?"),
|
||||
result.get("config_kept", False)))
|
||||
ok += 1
|
||||
|
||||
print("\n成功 %d / 失败 %d" % (ok, len(failed)))
|
||||
if failed:
|
||||
print("失败: " + " ".join(failed))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,13 +1,16 @@
|
||||
// Package meta 收集 HomeAgent 内核的全部元数据。
|
||||
// 版本号通过 `go build -ldflags` 注入,默认值为 dev 版本。
|
||||
// 此文件是 ABI 版本号与 dispatch method ID 的唯一数据源。
|
||||
// SDK 仓的 meta/meta.go 应与此保持同步。
|
||||
package meta
|
||||
|
||||
var (
|
||||
// Version 是 HomeAgent 内核版本号。
|
||||
// 通过 `-ldflags="-X gitcode.com/JianFeeeee/HomeAgent/internal/meta.Version=vX.Y.Z"` 注入。
|
||||
Version = "0.9.1"
|
||||
//
|
||||
// 1.0.0:外部插件从 C ABI 动态库迁到子进程 + 共享内存。
|
||||
// 这是首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件必须
|
||||
// 用新版 plugindev 重编),故跃到主版本号。
|
||||
Version = "1.0.0"
|
||||
|
||||
// Commit 是构建时的 Git commit hash。
|
||||
Commit = "unknown"
|
||||
@ -19,7 +22,7 @@ var (
|
||||
KernelName = "HomeAgent"
|
||||
|
||||
// SDKCompatibleVersion 是此内核可兼容的最高 SDK 版本(semver)。
|
||||
SDKCompatibleVersion = "0.9.1"
|
||||
SDKCompatibleVersion = "1.0.0"
|
||||
)
|
||||
|
||||
// FullVersion 返回完整的版本字符串。
|
||||
@ -27,81 +30,15 @@ func FullVersion() string {
|
||||
return KernelName + " v" + Version + " (" + Commit + ")"
|
||||
}
|
||||
|
||||
// ---- ABI 版本(C ABI 协议版本,插件与内核通信用) ----
|
||||
// ABI 版本直接取内核版本号字符串(semver),与核心 Version 保持一致,不再使用独立数字编码。
|
||||
// 协商层(C 结构体 int version 字段)使用 CABINum:由版本字符串派生的整数(major*100 + minor)。
|
||||
// 映射:v0.8.x → CABINum=800;v0.9.x → CABINum=900(invoke_stage 写回)。
|
||||
// 小版本(patch)演进不影响 ABI,CABINum 不变。version_min 保证旧 ABI 插件仍可加载。
|
||||
|
||||
var (
|
||||
// ABIVersion 是 ABI 标识版本(字符串 semver,与核心 Version 对齐)。
|
||||
ABIVersion = Version
|
||||
// ABIVersionMin 是兼容的最低 ABI 标识版本。
|
||||
ABIVersionMin = "0.8.0"
|
||||
)
|
||||
|
||||
const (
|
||||
// CABINum 是 C 层协商用的整数版本(major*100 + minor),随 ABIVersion 派生。
|
||||
CABINum = 900
|
||||
// CABINumMin 是 C 层兼容的最低整数版本。
|
||||
// 旧工具链(v0.8 之前)写入的整数 version=1,无写回能力但与新内核结构兼容,
|
||||
// 因此最小值保持 1 以兼容全部旧插件(新插件 900 匹配,旧插件 1/2 通过);
|
||||
// 仅当未来内核 ABI 破坏兼容时才提高该值。
|
||||
CABINumMin = 1
|
||||
)
|
||||
|
||||
// ---- Dispatch Method IDs ----
|
||||
// 核心→插件:这些 ID 通过 CoreAPI.dispatch 传递,标识 SDK 调用。
|
||||
// 插件端的 C enum 定义在 plugindev 的 C ABI header 模板中。
|
||||
const (
|
||||
CoreRegisterTool = 1
|
||||
CoreRegisterStage = 2
|
||||
CoreRegisterOutputCh = 3
|
||||
CoreRegisterPluginAPI = 4
|
||||
CoreInjectText = 5
|
||||
CoreInjectInterruptText = 6
|
||||
CoreInjectTextNoMemory = 7
|
||||
CoreSetAutoRestart = 8
|
||||
CoreMemoryRecall = 9
|
||||
CoreMemoryCommit = 10
|
||||
CoreMemoryIntrospect = 11
|
||||
CoreMemoryMerge = 12
|
||||
CoreMemoryPurge = 13
|
||||
CoreDocQuery = 14
|
||||
CoreKnowledgeSearch = 15
|
||||
CoreSettingsGet = 16
|
||||
CoreSettingsSet = 17
|
||||
CoreSettingsRegisterDef = 18
|
||||
CoreLLMListSources = 19
|
||||
CoreLLMSetSource = 20
|
||||
CoreSocialGetPerson = 21
|
||||
CoreSocialGetNetwork = 22
|
||||
CoreSubscribe = 23
|
||||
CoreUnsubscribe = 24
|
||||
CoreFreeString = 25
|
||||
CoreSettingsGetCore = 26
|
||||
CoreSettingsSetCore = 27
|
||||
CoreSettingsListCore = 28
|
||||
CoreSettingsGetPlugin = 29
|
||||
CoreSettingsSetPlugin = 30
|
||||
CoreSettingsListPlugin = 31
|
||||
CoreDocInsert = 32
|
||||
CoreDocRemove = 33
|
||||
CoreDocStats = 34
|
||||
CoreKnowledgeAdd = 35
|
||||
CoreKnowledgeList = 36
|
||||
CoreLLMCurrentSource = 37
|
||||
CoreSocialGetTrait = 38
|
||||
CoreSocialGetRelations = 39
|
||||
CoreSocialListPersons = 40
|
||||
CoreTextMemoryAppend = 41
|
||||
CoreSettingsList = 42
|
||||
CoreSettingsDefs = 43
|
||||
CoreSettingsDump = 44
|
||||
CoreSettingsPlugins = 45
|
||||
CoreRegisterInputCh = 46
|
||||
CoreInjectInputSync = 47
|
||||
CorePluginReloadOne = 48
|
||||
CorePluginListLoaded = 49
|
||||
CorePluginIsDisabled = 50
|
||||
)
|
||||
// ---- 协议版本 ----
|
||||
//
|
||||
// 子进程 RPC 的协议版本是一个独立的小整数,与内核语义版本解耦:
|
||||
// 语义版本变动频繁(修 bug、加字段),而 wire 协议只在**帧格式或握手语义**
|
||||
// 变化时才升。当前值见 internal/plugin/proc/protocol.go 的 ProtocolVersion。
|
||||
//
|
||||
// C ABI 时代的 ABIVersion / CABINum / 51 个 Core<Method> 整数 ID 已随
|
||||
// Part 6.2 删除 internal/plugin/cabi/ 一并退场:
|
||||
// - 整数 method id 平移为 method 名字符串(proc/protocol.go 的 Method* 常量)
|
||||
// - 版本协商改为握手帧里的 protocol 字段
|
||||
//
|
||||
// 保留那些常量只会让人以为它们还在生效。
|
||||
|
||||
Reference in New Issue
Block a user