| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
fix(lua-sdk): 同步注入在 Lua 中明确标记为不可用(避免自锁) sdk.inject_input_sync / *_sync_opts / inject_input_media_sync* 要等本轮回复, 而 Lua 代码只在持有插件锁的回调里执行 ⇒ 必然自锁。mock 不再假装返回 (reply,nil),改为与内核一致的明确错误,避免离线测试误以为可用。 | 1 个月前 | |
feat(hmapdev): 构建期告警「同一 stage 被重复注册」+ qq 合并写法 ## 用户指出的关键:告警要给**开发者**看,所以必须在构建期 我第一版把告警放进了工具链生成的 proc_main 模板(插件**运行期**打日志)。 用户直接指出:「本身工具链这个编译器警告就是给开发者看的」——运行期日志 是给运维/用户看的,开发者看不到。已撤销,改为构建期静态检查。 ## 缺陷(真实事故,2026-10-18) 插件侧把 stage handler 存在 map[string]sdk.StageHandler(stage 名为键)⇒ **同一 stage 注册两次,后者静默覆盖前者**。 QQ 插件当时写成: RegisterStage(BeforeToolcall, 权限门) ← 被下一条覆盖 RegisterStage(BeforeToolcall, 切状态) ← 实际生效 后果不只是状态不切换 —— **权限门被静默关掉了**(那几版部署里任何人 发消息都能调任何工具)。编译器不报、运行时不报错、内核日志还显示 2 handlers [qq qq](看着完全正常)。只有静态检查能发现。 ## 实现 · stage_dup_check.go:扫构建目录下的 .go(排除 _test.go 与 z_proc*), 统计每个 stage 常量被 RegisterStage 引用几次,>1 就告警 · 接入 buildTarget 开头 ⇒ 所有 target(含 lua)都检查 · **只告警不中断**:存量插件里重复注册可能是本意,直接 fail 会让它们 升 SDK 后无法构建 · example/qq:三个重复 stage 合并成单 handler(权限门 + 状态切换) ## 判据(4 条) · 同 stage 两次 ⇒ 报出来 · 不同 stage 各一次 ⇒ 不误报 · **注释里的示例注册不计入** · 测试文件 / 工具链生成文件不计入 ★ 「注释」这条是这个检查器**第一版自己栽的坑**:直接对全文跑正则, 把插件里「当初写错了、留在注释里当教训」的那几行也统计进去,于是 修好之后照样报「注册了 3 次」(实测)。现在逐行跳 // 与跨行 /* */。 本项目已多次绊倒在「注释里有、代码里没有」上,这次轮到检查器自己。 ## 实测 qq 修好后构建: packaged qq_linux_amd64.hmap(无告警) 故意加一条重复: ⚠️ 警告: stage StageBeforeOutput 注册了 2 次 建议:把多个功能合并进同一个 handler | 11 小时前 | |
feat(sdk): 22 个示例插件配齐 skill + 三态启动声明 + 打包期校验 ## 用户的三条要求 1. 「所有插件都默认自启会占启动时间,还在 llms 上下文塞大量工具」 ⇒ 纯工具型改为按需加载,靠 skill 让 agent 知道要先拉起。 2. 「会向 agent 注入输入的部分插件不能这样做」 ⇒ 9 个必须常驻,其余 13 个按需。 3. 「sdk 是不是没有暴露插件声明自身行为的方案」 ⇒ 对:auto_start 只在 internal/plugin/manifest.go 有,SDK 零命中。 ## 定级依据(代码证据,不是猜描述) 注入 / 钩子 ⇒ 必须常驻(9 个): qq HTTP 监听 NapCat 推送,消息到达要立刻注入 rss 轮询订阅源,新文章主动推送 calendar 30s ticker 检查提醒 memo 5min ticker 检查待办到期 a2a TCP 监听外部 Agent 请求 acp TCP 监听 ACP 客户端 browser ticker + 长命令完成回调 sanitizer 注册 stage 钩子(无工具,要每轮生效) luademo 注册 stage 钩子做示范 纯工具型 ⇒ 按需(13 个): weather ocr music bili ai_image files editdoc deepsearch plugindev recoverydiag vanblog vikunja commondev ★ commondev 值得单独说:它虽然有 6 处 Inject 调用,但**注入源是自己的 sidecar 完成回投**,而 sidecar 只在 commondev_agent_up 时才拉起 (Start() 里不启 sidecar,通道也在 agent_up 时才注册)。所以没有 agent 调用它时,它不需要做任何事 —— 可以按需。44 个工具 ≈726 tokens 省下来可观。 ## 改动 · 22 个插件各配一个 skill,写明:何时用、先 plugin_start、 工具清单、**用 plugin_stop 而不是 plugin_disable** · 13 个 plg.json 加 auto_start: false · sdk/autostart.go:把三态语义与"按需插件必须有 skill"的理由写进公开 SDK · hmapdev 打包期校验:auto_start=false 但无 skill/ 目录 ⇒ 直接 exit 1 (理由:缺 skill 的按需插件**等价于没装**,比没装更糟 —— 占着磁盘让人以为可用) · 修 Lua 打包路径:它**完全不走** rawfile/skill 收集,luademo 的 skill 永远打不进去 · 修 build-examples.sh:原来 [ ! -x ] 判"二进制在不在",改了 hmapdev 源码后 重新打包**仍用旧工具链**(实测:luademo 的 skill 一直是 0) · 补 mocksdk.ToolDef.Timeout(上一轮加 ToolDef.Timeout 时漏了镜像) ## 判据 · TestHasSkillDirRequiresRealSkillMd(空目录/.gitkeep/无 SKILL.md 都不算) · TestBuildRejectsOnDemandWithoutSkill(打包期必须真拦住 + exit 1) · TestLuaPackagingCollectsSkillAndRawfiles(**验代码行**,不验注释 —— 第一版 只查符号存在,把 createHmapWithRaw 换回 createHmap 判据照样通过) ## 踩过的坑 · Lua 分支的结束边界原是 // Resolve build config,但那个注释在文件里 **出现多次**(Go 路径里也有),用它截取会把 Go 分支一起包进来 ⇒ 变异测不出。改成截到 Lua 分支自己的 return + }。 | 13 小时前 | |
feat(sdk): 22 个示例插件配齐 skill + 三态启动声明 + 打包期校验 ## 用户的三条要求 1. 「所有插件都默认自启会占启动时间,还在 llms 上下文塞大量工具」 ⇒ 纯工具型改为按需加载,靠 skill 让 agent 知道要先拉起。 2. 「会向 agent 注入输入的部分插件不能这样做」 ⇒ 9 个必须常驻,其余 13 个按需。 3. 「sdk 是不是没有暴露插件声明自身行为的方案」 ⇒ 对:auto_start 只在 internal/plugin/manifest.go 有,SDK 零命中。 ## 定级依据(代码证据,不是猜描述) 注入 / 钩子 ⇒ 必须常驻(9 个): qq HTTP 监听 NapCat 推送,消息到达要立刻注入 rss 轮询订阅源,新文章主动推送 calendar 30s ticker 检查提醒 memo 5min ticker 检查待办到期 a2a TCP 监听外部 Agent 请求 acp TCP 监听 ACP 客户端 browser ticker + 长命令完成回调 sanitizer 注册 stage 钩子(无工具,要每轮生效) luademo 注册 stage 钩子做示范 纯工具型 ⇒ 按需(13 个): weather ocr music bili ai_image files editdoc deepsearch plugindev recoverydiag vanblog vikunja commondev ★ commondev 值得单独说:它虽然有 6 处 Inject 调用,但**注入源是自己的 sidecar 完成回投**,而 sidecar 只在 commondev_agent_up 时才拉起 (Start() 里不启 sidecar,通道也在 agent_up 时才注册)。所以没有 agent 调用它时,它不需要做任何事 —— 可以按需。44 个工具 ≈726 tokens 省下来可观。 ## 改动 · 22 个插件各配一个 skill,写明:何时用、先 plugin_start、 工具清单、**用 plugin_stop 而不是 plugin_disable** · 13 个 plg.json 加 auto_start: false · sdk/autostart.go:把三态语义与"按需插件必须有 skill"的理由写进公开 SDK · hmapdev 打包期校验:auto_start=false 但无 skill/ 目录 ⇒ 直接 exit 1 (理由:缺 skill 的按需插件**等价于没装**,比没装更糟 —— 占着磁盘让人以为可用) · 修 Lua 打包路径:它**完全不走** rawfile/skill 收集,luademo 的 skill 永远打不进去 · 修 build-examples.sh:原来 [ ! -x ] 判"二进制在不在",改了 hmapdev 源码后 重新打包**仍用旧工具链**(实测:luademo 的 skill 一直是 0) · 补 mocksdk.ToolDef.Timeout(上一轮加 ToolDef.Timeout 时漏了镜像) ## 判据 · TestHasSkillDirRequiresRealSkillMd(空目录/.gitkeep/无 SKILL.md 都不算) · TestBuildRejectsOnDemandWithoutSkill(打包期必须真拦住 + exit 1) · TestLuaPackagingCollectsSkillAndRawfiles(**验代码行**,不验注释 —— 第一版 只查符号存在,把 createHmapWithRaw 换回 createHmap 判据照样通过) ## 踩过的坑 · Lua 分支的结束边界原是 // Resolve build config,但那个注释在文件里 **出现多次**(Go 路径里也有),用它截取会把 Go 分支一起包进来 ⇒ 变异测不出。改成截到 Lua 分支自己的 return + }。 | 13 小时前 | |
feat(hmapdev): 构建期告警「同一 stage 被重复注册」+ qq 合并写法 ## 用户指出的关键:告警要给**开发者**看,所以必须在构建期 我第一版把告警放进了工具链生成的 proc_main 模板(插件**运行期**打日志)。 用户直接指出:「本身工具链这个编译器警告就是给开发者看的」——运行期日志 是给运维/用户看的,开发者看不到。已撤销,改为构建期静态检查。 ## 缺陷(真实事故,2026-10-18) 插件侧把 stage handler 存在 map[string]sdk.StageHandler(stage 名为键)⇒ **同一 stage 注册两次,后者静默覆盖前者**。 QQ 插件当时写成: RegisterStage(BeforeToolcall, 权限门) ← 被下一条覆盖 RegisterStage(BeforeToolcall, 切状态) ← 实际生效 后果不只是状态不切换 —— **权限门被静默关掉了**(那几版部署里任何人 发消息都能调任何工具)。编译器不报、运行时不报错、内核日志还显示 2 handlers [qq qq](看着完全正常)。只有静态检查能发现。 ## 实现 · stage_dup_check.go:扫构建目录下的 .go(排除 _test.go 与 z_proc*), 统计每个 stage 常量被 RegisterStage 引用几次,>1 就告警 · 接入 buildTarget 开头 ⇒ 所有 target(含 lua)都检查 · **只告警不中断**:存量插件里重复注册可能是本意,直接 fail 会让它们 升 SDK 后无法构建 · example/qq:三个重复 stage 合并成单 handler(权限门 + 状态切换) ## 判据(4 条) · 同 stage 两次 ⇒ 报出来 · 不同 stage 各一次 ⇒ 不误报 · **注释里的示例注册不计入** · 测试文件 / 工具链生成文件不计入 ★ 「注释」这条是这个检查器**第一版自己栽的坑**:直接对全文跑正则, 把插件里「当初写错了、留在注释里当教训」的那几行也统计进去,于是 修好之后照样报「注册了 3 次」(实测)。现在逐行跳 // 与跨行 /* */。 本项目已多次绊倒在「注释里有、代码里没有」上,这次轮到检查器自己。 ## 实测 qq 修好后构建: packaged qq_linux_amd64.hmap(无告警) 故意加一条重复: ⚠️ 警告: stage StageBeforeOutput 注册了 2 次 建议:把多个功能合并进同一个 handler | 11 小时前 | |
refactor(toolchain)!: 工具链 plugindev 更名为 hmapdev,module path 改回 gitcode - 目录 tools/plugindev → tools/hmapdev,可执行文件名/平台产物名同步 (hmapdev_linux_amd64 等;包格式仍叫 .hmap) - module path github.com/JianFeeeee/homeagent-sdk/tools/... → gitcode.com/... (与仓库实际托管一致;核心仓不依赖该 path,改动无外部影响) - SDK 存储目录 ~/.homeagent/plugindev/sdk → ~/.homeagent/hmapdev/sdk 新目录不存在而旧目录存在时沿用旧目录 → 已装 SDK 版本不会丢失 - 命令表/usage/--help/生成项目 README/示例 README/NSIS 安装器/ package/build.sh/build-examples.sh 全部同步;PLUGINDEV 环境变量保留兼容 - sdk/ 目录零改动(公开接口不变) 验证: - go build ./... ok;go test ./tools/hmapdev/ ok(含模板接线守卫 TestProcTemplate_CoversAllCoreMethods) - bash -n package/{build,build-examples}.sh ok - 端到端:hmapdev init demo && hmapdev build → dist/demo_bundle.hmap(linux+darwin) - 本机安装 /usr/local/bin/hmapdev,旧名以软链保留;sdk list/current 正常 | 1 个月前 | |
chore(module)!: module 路径随托管地迁移 gitcode → github SDK 仓已迁到 GitHub 作为权威源,go module 路径同步: gitcode.com/JianFeeeee/homeagent-sdk → github.com/JianFeeeee/homeagentsdk ## 范围 74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。 主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上** (见下),所以那次的批量替换扫不到它们——这里是补偿。 ## 为何这批文件当时不在工作树上(事故与恢复) 10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件 误清理了(example/*/plugin.go + plg.json,10 个示例)。 根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 third_party/ 里的 **独立 git 仓库**,而主仓 .gitignore 忽略了 example/,于是它在主仓 git status 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。 已全部恢复(git checkout -- <D 文件>,只恢复删除、不动迁移改动), 20 个文件与 HEAD 逐字节一致。 **破坏的连锁后果**:恢复出来的 plugin.go 仍写着旧 module 路径, 因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。 ## 保留 gitcode 引用 以下位置的 gitcode **有意保留**,不要"顺手清掉": - README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明 - mkdocs.yml 的 gitcode 镜像链接(国内直连更快) - tools/hmapdev/templates.go 里「SDK 已从 gitcode 迁到 GitHub」的注释 gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。 ## 验证 - 本仓根 module:go build ./... + go test ./... 全通过 - 全部 20 个 example:go vet 通过(补偿前 10 个因旧 module 路径失败) - example/qq:go test 通过,且能从源码 build 出可用 plugin 产物 | 9 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
chore(module)!: module 路径随托管地迁移 gitcode → github SDK 仓已迁到 GitHub 作为权威源,go module 路径同步: gitcode.com/JianFeeeee/homeagent-sdk → github.com/JianFeeeee/homeagentsdk ## 范围 74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。 主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上** (见下),所以那次的批量替换扫不到它们——这里是补偿。 ## 为何这批文件当时不在工作树上(事故与恢复) 10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件 误清理了(example/*/plugin.go + plg.json,10 个示例)。 根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 third_party/ 里的 **独立 git 仓库**,而主仓 .gitignore 忽略了 example/,于是它在主仓 git status 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。 已全部恢复(git checkout -- <D 文件>,只恢复删除、不动迁移改动), 20 个文件与 HEAD 逐字节一致。 **破坏的连锁后果**:恢复出来的 plugin.go 仍写着旧 module 路径, 因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。 ## 保留 gitcode 引用 以下位置的 gitcode **有意保留**,不要"顺手清掉": - README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明 - mkdocs.yml 的 gitcode 镜像链接(国内直连更快) - tools/hmapdev/templates.go 里「SDK 已从 gitcode 迁到 GitHub」的注释 gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。 ## 验证 - 本仓根 module:go build ./... + go test ./... 全通过 - 全部 20 个 example:go vet 通过(补偿前 10 个因旧 module 路径失败) - example/qq:go test 通过,且能从源码 build 出可用 plugin 产物 | 9 天前 | |
fix(hmapdev): SDK 模块识别要认新旧两个 module 名(迁移漏改的逻辑判定) 上一提交(b6a6168)只替换了 import 路径,**漏了用旧 module 名做逻辑判定 的五处**。CI 当场抓到一处( TestPrintVersionReportsInjectedVersion), 但那是测试断言;另外三处是**产品逻辑**,CI 抓不到,是静默失效。 ## 漏改的地方 | 位置 | 原判定 | 失效后果 | |---|---|---| | cmd_build.go | Contains(mod,"homeagent-sdk") | 认不出 SDK 依赖 ⇒ **不写 replace** | | cmd_skill.go | Contains(gm,"homeagent-sdk") | 认不出 SDK 仓 ⇒ 不安装 skills | | yaegi/interp.go ×2 | Contains(line,"homeagent-sdk") | findSDKGoPath 找不到 SDK ⇒ 退化到兜底 | 迁移后这些 Contains **恒为假**(新名是 homeagentsdk,没有连字符)。 三者都**不报错**,只是行为悄悄变成另一条分支——比直接崩更难发现。 ## 关键:不能只认新名 本机实测存量插件工作区的 go.mod **全部仍是旧名**: /home/program/qq-plugin (无 go.mod) /home/program/vanblog-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/mcquery-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/fileproc-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/agentmail/plugins/ require gitcode.com/JianFeeeee/homeagent-sdk 用户已创建的项目不会因为我们改仓就自动更新。所以判定必须 **两种都接受**,而不是把旧名换成新名(那只是把 bug 从"认不出新"换成"认不出旧")。 ## 改动 - meta 补 SDKModule / LegacySDKModule 常量(原注释引用了 SDKModule 但常量并不存在——注释先于实现) - 新增 tools/hmapdev/sdkmodule.go:isSDKModule / sdkModulePath / containsSDKModule - 新增 tools/hmapdev/yaegi/sdkmodule.go:同语义的最小副本 (yaegi 是独立子包,不能 import 父包) - 三处逻辑判定改用上述助手 ## 判据(可杀变异,逐个实测) sdkmodule_test.go + yaegi/sdkmodule_test.go:覆盖新名/旧名/带版本号的 旧名/空串/核心仓(HomeAgent,不是 SDK)/无关模块。 这三个判定点**此前零测试覆盖**——这正是它们能静默失效的原因。 变异验证: - 只认新名 → FAIL「旧名漏认会让存量插件静默走错分支」 - containsSDKModule 只认新名 → FAIL「必须认旧名」 ## 验证 - SDK 根 module + hmapdev module:go build / go test 全通过 - 20 个 example go vet 全通过 - go mod verify → all modules verified | 9 天前 | |
feat(hmapdev): skill 子命令 —— 插件开发知识随 SDK 分发 ## 为什么需要 HomeAgent 插件开发的知识(hmapdev 工具链、SDK 版本坑、plg.json、 plugin.bin 部署、内置 vs 独立二进制)此前只存在于**某个 agent 的对话历史**里。 本机四个 agent(pi / claude / codex / .agents)都不会开发插件, 因为它们**没有任何渠道**拿到这些知识。 知识属于 SDK(与工具链同源、随 SDK 版本走),所以: - 源: skills/(随 SDK 仓分发) - 装:hmapdev skill install ## 命令 hmapdev skill list 列出活跃 SDK 里的 skills hmapdev skill install [name...] 装到各 agent 的 skills 目录 hmapdev skill path 显示源目录与来源 安装目标(**只装这些**,不认识的目录不创建): ~/.pi/agent/skills ~/.claude/skills ~/.codex/skills ~/.agents/skills ## 两条策略,与别处**故意相反** 1. **总是覆盖**。skill 是**工具生成物**,不是用户数据。保留用户改过的版本 会让它与工具链脱节 —— 而工具链的命令面会随版本变。 (对比:hmapdev build 写出的适配器更新会**保护**用户修改, 因为那是运行时代码;skill 只是说明文档。) 2. **不扫 glob 自动发现**。不认识的目录建出来也没用, 还会让用户以为装上了。 ## 源目录:仓库优先,store 回退 store(~/.homeagent/hmapdev/sdk/<v>/)是 sdk install 复制的**副本**。 而 skill 是纯文档、加它不需要动 SDK 的编译产物 —— 「改了 skill 却要重装 SDK 才能生效」对日常维护不合理。 故优先用 hmapdev **自身所在仓库**的 skills/(靠可执行文件位置反推, 并校验 go.mod 的 module 是 homeagent-sdk),落空才回退 store。 ## 判据 9 条(cmd_skill_test.go) 覆盖:目标无重复/不逃出 home、**真实仓库**里 skills/ 的布局合规、 隐藏目录与普通文件不算 skill、幂等、覆盖用户修改、跳过未知目标、 真实入口 cmdSkillInstall 覆盖用户修改、未知 skill 名不静默。 ### 写判据时踩的三个坑 1. **TestSkillSourceHasValidLayout 原用 t.TempDir()** ⇒ 那个目录是空的, 判据永远红,且红得毫无意义("文件不存在"是真的,但真实文件在 SDK 仓里)。 改为指向**真实仓库**。 2. **fixture 造错**:我 MkdirAll 出一个叫 README.md 的**目录**, 于是实现"正确地"把它当 skill,判据却报「把 README.md 当成了 skill」。 是 fixture 错,不是实现错。 3. ★ **TestSkillInstallOverwritesUserEdit 只验 copyDir、没走真实入口** ⇒ 我把 cmdSkillInstall 里的 copyDir 换成「已存在就跳过」, **判据依然全绿**。变异测试抓出来的。补 TestCmdSkillInstallOverwritesUserEdit 走真实入口后,变异立刻变红。 ## 实测 删掉三处 skill 后 hmapdev skill install 一条命令装回四处, 四处 md5 与源一致,二次安装结果不变(幂等)。 ## 门禁 - go test ./...(hmapdev 独立 module):ok,0 FAIL - go build ./...:ok - 9 条判据全通过,变异测试确认能抓回归 | 20 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
chore(module)!: module 路径随托管地迁移 gitcode → github SDK 仓已迁到 GitHub 作为权威源,go module 路径同步: gitcode.com/JianFeeeee/homeagent-sdk → github.com/JianFeeeee/homeagentsdk ## 范围 74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。 主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上** (见下),所以那次的批量替换扫不到它们——这里是补偿。 ## 为何这批文件当时不在工作树上(事故与恢复) 10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件 误清理了(example/*/plugin.go + plg.json,10 个示例)。 根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 third_party/ 里的 **独立 git 仓库**,而主仓 .gitignore 忽略了 example/,于是它在主仓 git status 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。 已全部恢复(git checkout -- <D 文件>,只恢复删除、不动迁移改动), 20 个文件与 HEAD 逐字节一致。 **破坏的连锁后果**:恢复出来的 plugin.go 仍写着旧 module 路径, 因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。 ## 保留 gitcode 引用 以下位置的 gitcode **有意保留**,不要"顺手清掉": - README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明 - mkdocs.yml 的 gitcode 镜像链接(国内直连更快) - tools/hmapdev/templates.go 里「SDK 已从 gitcode 迁到 GitHub」的注释 gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。 ## 验证 - 本仓根 module:go build ./... + go test ./... 全通过 - 全部 20 个 example:go vet 通过(补偿前 10 个因旧 module 路径失败) - example/qq:go test 通过,且能从源码 build 出可用 plugin 产物 | 9 天前 | |
refactor(toolchain)!: 工具链 plugindev 更名为 hmapdev,module path 改回 gitcode - 目录 tools/plugindev → tools/hmapdev,可执行文件名/平台产物名同步 (hmapdev_linux_amd64 等;包格式仍叫 .hmap) - module path github.com/JianFeeeee/homeagent-sdk/tools/... → gitcode.com/... (与仓库实际托管一致;核心仓不依赖该 path,改动无外部影响) - SDK 存储目录 ~/.homeagent/plugindev/sdk → ~/.homeagent/hmapdev/sdk 新目录不存在而旧目录存在时沿用旧目录 → 已装 SDK 版本不会丢失 - 命令表/usage/--help/生成项目 README/示例 README/NSIS 安装器/ package/build.sh/build-examples.sh 全部同步;PLUGINDEV 环境变量保留兼容 - sdk/ 目录零改动(公开接口不变) 验证: - go build ./... ok;go test ./tools/hmapdev/ ok(含模板接线守卫 TestProcTemplate_CoversAllCoreMethods) - bash -n package/{build,build-examples}.sh ok - 端到端:hmapdev init demo && hmapdev build → dist/demo_bundle.hmap(linux+darwin) - 本机安装 /usr/local/bin/hmapdev,旧名以软链保留;sdk list/current 正常 | 1 个月前 | |
feat(sdk): 22 个示例插件配齐 skill + 三态启动声明 + 打包期校验 ## 用户的三条要求 1. 「所有插件都默认自启会占启动时间,还在 llms 上下文塞大量工具」 ⇒ 纯工具型改为按需加载,靠 skill 让 agent 知道要先拉起。 2. 「会向 agent 注入输入的部分插件不能这样做」 ⇒ 9 个必须常驻,其余 13 个按需。 3. 「sdk 是不是没有暴露插件声明自身行为的方案」 ⇒ 对:auto_start 只在 internal/plugin/manifest.go 有,SDK 零命中。 ## 定级依据(代码证据,不是猜描述) 注入 / 钩子 ⇒ 必须常驻(9 个): qq HTTP 监听 NapCat 推送,消息到达要立刻注入 rss 轮询订阅源,新文章主动推送 calendar 30s ticker 检查提醒 memo 5min ticker 检查待办到期 a2a TCP 监听外部 Agent 请求 acp TCP 监听 ACP 客户端 browser ticker + 长命令完成回调 sanitizer 注册 stage 钩子(无工具,要每轮生效) luademo 注册 stage 钩子做示范 纯工具型 ⇒ 按需(13 个): weather ocr music bili ai_image files editdoc deepsearch plugindev recoverydiag vanblog vikunja commondev ★ commondev 值得单独说:它虽然有 6 处 Inject 调用,但**注入源是自己的 sidecar 完成回投**,而 sidecar 只在 commondev_agent_up 时才拉起 (Start() 里不启 sidecar,通道也在 agent_up 时才注册)。所以没有 agent 调用它时,它不需要做任何事 —— 可以按需。44 个工具 ≈726 tokens 省下来可观。 ## 改动 · 22 个插件各配一个 skill,写明:何时用、先 plugin_start、 工具清单、**用 plugin_stop 而不是 plugin_disable** · 13 个 plg.json 加 auto_start: false · sdk/autostart.go:把三态语义与"按需插件必须有 skill"的理由写进公开 SDK · hmapdev 打包期校验:auto_start=false 但无 skill/ 目录 ⇒ 直接 exit 1 (理由:缺 skill 的按需插件**等价于没装**,比没装更糟 —— 占着磁盘让人以为可用) · 修 Lua 打包路径:它**完全不走** rawfile/skill 收集,luademo 的 skill 永远打不进去 · 修 build-examples.sh:原来 [ ! -x ] 判"二进制在不在",改了 hmapdev 源码后 重新打包**仍用旧工具链**(实测:luademo 的 skill 一直是 0) · 补 mocksdk.ToolDef.Timeout(上一轮加 ToolDef.Timeout 时漏了镜像) ## 判据 · TestHasSkillDirRequiresRealSkillMd(空目录/.gitkeep/无 SKILL.md 都不算) · TestBuildRejectsOnDemandWithoutSkill(打包期必须真拦住 + exit 1) · TestLuaPackagingCollectsSkillAndRawfiles(**验代码行**,不验注释 —— 第一版 只查符号存在,把 createHmapWithRaw 换回 createHmap 判据照样通过) ## 踩过的坑 · Lua 分支的结束边界原是 // Resolve build config,但那个注释在文件里 **出现多次**(Go 路径里也有),用它截取会把 Go 分支一起包进来 ⇒ 变异测不出。改成截到 Lua 分支自己的 return + }。 | 13 小时前 | |
feat(lua): Lua SDK 全量对齐 1.3.0 + hmapdev 单一 mock 源/版本标记/语法预检 内核侧 Lua 桥此前停在 v0.8.0 时代能力面,而 1.1/1.2/1.3 新增的 媒体、注入标志位、中断优先级、事件订阅、动态通道注销只在 Go 侧存在, 文档却宣称『能力完全对齐』——属于静默漂移。 本仓(事实源): - 新增 sdk/lua/sdk.lua:Lua mock 的单一事实源,补齐全部新 API (*_opts / inject_input_sync / inject_*_media / set_tool_blocks / unregister_output_channel / events / plugin_mgr / insert_with_media / sentence_text+media_digests / attachments / context_policy)。 - scripts/sync-lua-sdk.sh:把事实源同步到 hmapdev assets、luademo、 以及被 vendored 时的内核副本;三份 sdk.lua 不再各自漂移。 - hmapdev init --lua:优先从激活 SDK 拷权威 mock,内嵌模板降级为 assets/sdk.lua 回退,不再内联手写副本。 - hmapdev build(Lua):plugin.json 写入 SDK 版本(能力可追溯), 打包前用 luac -p / lua loadfile 做语法预检,失败以非零码退出。 - hmapdev debug --lua:优先用激活 SDK 的权威 mock(HMAPDEV_SDK_LUA)。 - luademo 升级为全能力示例(新增 luademo_probe_v2)。 | 1 个月前 | |
chore(module)!: module 路径随托管地迁移 gitcode → github SDK 仓已迁到 GitHub 作为权威源,go module 路径同步: gitcode.com/JianFeeeee/homeagent-sdk → github.com/JianFeeeee/homeagentsdk ## 范围 74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。 主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上** (见下),所以那次的批量替换扫不到它们——这里是补偿。 ## 为何这批文件当时不在工作树上(事故与恢复) 10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件 误清理了(example/*/plugin.go + plg.json,10 个示例)。 根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 third_party/ 里的 **独立 git 仓库**,而主仓 .gitignore 忽略了 example/,于是它在主仓 git status 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。 已全部恢复(git checkout -- <D 文件>,只恢复删除、不动迁移改动), 20 个文件与 HEAD 逐字节一致。 **破坏的连锁后果**:恢复出来的 plugin.go 仍写着旧 module 路径, 因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。 ## 保留 gitcode 引用 以下位置的 gitcode **有意保留**,不要"顺手清掉": - README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明 - mkdocs.yml 的 gitcode 镜像链接(国内直连更快) - tools/hmapdev/templates.go 里「SDK 已从 gitcode 迁到 GitHub」的注释 gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。 ## 验证 - 本仓根 module:go build ./... + go test ./... 全通过 - 全部 20 个 example:go vet 通过(补偿前 10 个因旧 module 路径失败) - example/qq:go test 通过,且能从源码 build 出可用 plugin 产物 | 9 天前 | |
fix(hmapdev): SDK 模块识别要认新旧两个 module 名(迁移漏改的逻辑判定) 上一提交(b6a6168)只替换了 import 路径,**漏了用旧 module 名做逻辑判定 的五处**。CI 当场抓到一处( TestPrintVersionReportsInjectedVersion), 但那是测试断言;另外三处是**产品逻辑**,CI 抓不到,是静默失效。 ## 漏改的地方 | 位置 | 原判定 | 失效后果 | |---|---|---| | cmd_build.go | Contains(mod,"homeagent-sdk") | 认不出 SDK 依赖 ⇒ **不写 replace** | | cmd_skill.go | Contains(gm,"homeagent-sdk") | 认不出 SDK 仓 ⇒ 不安装 skills | | yaegi/interp.go ×2 | Contains(line,"homeagent-sdk") | findSDKGoPath 找不到 SDK ⇒ 退化到兜底 | 迁移后这些 Contains **恒为假**(新名是 homeagentsdk,没有连字符)。 三者都**不报错**,只是行为悄悄变成另一条分支——比直接崩更难发现。 ## 关键:不能只认新名 本机实测存量插件工作区的 go.mod **全部仍是旧名**: /home/program/qq-plugin (无 go.mod) /home/program/vanblog-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/mcquery-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/fileproc-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/agentmail/plugins/ require gitcode.com/JianFeeeee/homeagent-sdk 用户已创建的项目不会因为我们改仓就自动更新。所以判定必须 **两种都接受**,而不是把旧名换成新名(那只是把 bug 从"认不出新"换成"认不出旧")。 ## 改动 - meta 补 SDKModule / LegacySDKModule 常量(原注释引用了 SDKModule 但常量并不存在——注释先于实现) - 新增 tools/hmapdev/sdkmodule.go:isSDKModule / sdkModulePath / containsSDKModule - 新增 tools/hmapdev/yaegi/sdkmodule.go:同语义的最小副本 (yaegi 是独立子包,不能 import 父包) - 三处逻辑判定改用上述助手 ## 判据(可杀变异,逐个实测) sdkmodule_test.go + yaegi/sdkmodule_test.go:覆盖新名/旧名/带版本号的 旧名/空串/核心仓(HomeAgent,不是 SDK)/无关模块。 这三个判定点**此前零测试覆盖**——这正是它们能静默失效的原因。 变异验证: - 只认新名 → FAIL「旧名漏认会让存量插件静默走错分支」 - containsSDKModule 只认新名 → FAIL「必须认旧名」 ## 验证 - SDK 根 module + hmapdev module:go build / go test 全通过 - 20 个 example go vet 全通过 - go mod verify → all modules verified | 9 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
refactor(toolchain)!: 工具链 plugindev 更名为 hmapdev,module path 改回 gitcode - 目录 tools/plugindev → tools/hmapdev,可执行文件名/平台产物名同步 (hmapdev_linux_amd64 等;包格式仍叫 .hmap) - module path github.com/JianFeeeee/homeagent-sdk/tools/... → gitcode.com/... (与仓库实际托管一致;核心仓不依赖该 path,改动无外部影响) - SDK 存储目录 ~/.homeagent/plugindev/sdk → ~/.homeagent/hmapdev/sdk 新目录不存在而旧目录存在时沿用旧目录 → 已装 SDK 版本不会丢失 - 命令表/usage/--help/生成项目 README/示例 README/NSIS 安装器/ package/build.sh/build-examples.sh 全部同步;PLUGINDEV 环境变量保留兼容 - sdk/ 目录零改动(公开接口不变) 验证: - go build ./... ok;go test ./tools/hmapdev/ ok(含模板接线守卫 TestProcTemplate_CoversAllCoreMethods) - bash -n package/{build,build-examples}.sh ok - 端到端:hmapdev init demo && hmapdev build → dist/demo_bundle.hmap(linux+darwin) - 本机安装 /usr/local/bin/hmapdev,旧名以软链保留;sdk list/current 正常 | 1 个月前 | |
refactor(sdk)!: 废除自定协议号,版本身份只由版本号本身承载 兼容性判据原有三处自造版本号,全部删除: - procProtocolVersion(模板侧握手常量) - shmVersion / shmOffVersion(共享段 header) - unifiedVersion(SuperBlock) 为什么废除:语义版本变动频繁(修 bug、加字段),再要求人去升一个额外的号, 那个号必然漏升而腐烂。实证:ProtocolVersion 常量在多次布局变更里只升过一次, 而对应的守卫测试(TestProcTemplate_ShmLayoutMatchesKernel)长期处于失败状态 无人察觉 —— 它当时期望 shmStageFieldCount=18/shmVersion=1,实际已是 19/2。 取而代之的两个判据都是**结构的派生事实**,不需要谁记得去同步: 1. 主版本号:内核 meta.Version ↔ 插件 sdk.SDKVersion 的主版本段比对。 主版本跃迁正是破坏性变更的定义(1.0.0 从 C ABI 改子进程即属此列), 所以它天然是正确分界。 2. field_count:StageContext 描述符个数,决定标志位基址。 顺带修正了 shmFlagsOff:原实现用本插件的编译期常量推算标志位基址, 内核加字段时会静默错位。现改为从 header 读取内核下发的描述符个数。 SuperBlock 的 version 槽位保留(不动其余偏移),值不再写入也不再校验。 判据:TestProcTemplate_RejectsIncompatiblePeer(含 banned 列表, 防这些号被重新引入)。 | 5 天前 | |
feat(sdk): 反代声明(ProxyDef / RegisterProxy)—— 插件声明服务,HomeAgent 反代出去 配套核心仓「webui 通用反向代理」。SDK 1.4.0 尚未发布,接口未冻结, 本次按开发期自由变更处理(正式发版时并入版本号推进)。 ## 声明契约 plugin.json 的 proxies 字段(声明式,静态可发现)或 RegisterProxy (运行期,供没有 plugin.json 的内置插件用): {"name":"ui","host":"myapp","path":"/p/myapp","strip_path":true, "target":"127.0.0.1:12100","auth":"homeagent"} 命名与既有能力对齐(ToolDef / ChannelDef / ConfigDef / RegisterTool / ToolRegistrar)——第一版写成 ProxyDecl / DeclareProxy / ProxyDeclarer 被评审指出「跟 SDK 其他接口不是一个风格」,已全面改名。 ## strip_path:Path 的两种语义 Path 不能一刀切成「原样保留」,真实需求有两种且**不能自动判定** (同一个 path 在两种语义下都说得通,猜错即全部 404 且像上游故障): strip_path 缺省/false(别名模式)—— path 是上游真实路径的一部分 /api/v1/device/ws + path=/api/v1/device → 上游收到原样 适用:客户端**已硬编码**路径的机器接口(设备网关即如此) strip_path=true(前缀模式)—— path 只是门户上的挂载点 /p/myapp/api/status + path=/p/myapp → 上游收到 /api/status 适用:自带 UI 的服务(前端用相对路径) 非法组合(strip_path 而无 path)被 ValidateProxyDef 挡住。 ## 单一入口原则(契约级要求) 一个声明 = 一个入口。两种挂载形态对「根路径」处理截然不同: Host 形态下根路径是插件的根(fetch('/api/x') 天然正确); Path 形态下根路径**属于门户**,同样代码会打到门户自己身上 (静默错路由:页面能开、功能全坏)。 故被反代的插件必须**一律使用相对路径**,绝不硬编码以 / 开头的绝对路径。 这样同一份前端在两种形态下都正确,插件不必知道自己被挂在哪, 反代层也能按外部条件(子域是否有证书/放行)自由选择形态。 ## 判据 sdk/proxy_test.go:两种语义的映射、非法组合、单一入口原则的契约存在性。 hmapdev proxy_config_test.go:schema 漂移保护(新增字段忘了同步就判红)、 非法声明在**打包时**就被拒(不必装到 HomeAgent 才看到)。 两模块 go test 全绿;文档站已重新生成(ProxyDef 与单一入口原则进入 docs/api/misc.md 与 llms-full.txt)。 ## 顺带修回的一处(此前随工作树丢失) writePluginJSON 漏写 proxies 键 —— 漏写的话插件装得上、启动正常、 就是不出现,没有任何报错。该 bug 曾在核心仓侧出现过(判据抓到过), 这次移植时由 TestWritePluginJSONPreservesProxies 再次判红并修复。 | 22 天前 | |
feat(sdk): 反代声明(ProxyDef / RegisterProxy)—— 插件声明服务,HomeAgent 反代出去 配套核心仓「webui 通用反向代理」。SDK 1.4.0 尚未发布,接口未冻结, 本次按开发期自由变更处理(正式发版时并入版本号推进)。 ## 声明契约 plugin.json 的 proxies 字段(声明式,静态可发现)或 RegisterProxy (运行期,供没有 plugin.json 的内置插件用): {"name":"ui","host":"myapp","path":"/p/myapp","strip_path":true, "target":"127.0.0.1:12100","auth":"homeagent"} 命名与既有能力对齐(ToolDef / ChannelDef / ConfigDef / RegisterTool / ToolRegistrar)——第一版写成 ProxyDecl / DeclareProxy / ProxyDeclarer 被评审指出「跟 SDK 其他接口不是一个风格」,已全面改名。 ## strip_path:Path 的两种语义 Path 不能一刀切成「原样保留」,真实需求有两种且**不能自动判定** (同一个 path 在两种语义下都说得通,猜错即全部 404 且像上游故障): strip_path 缺省/false(别名模式)—— path 是上游真实路径的一部分 /api/v1/device/ws + path=/api/v1/device → 上游收到原样 适用:客户端**已硬编码**路径的机器接口(设备网关即如此) strip_path=true(前缀模式)—— path 只是门户上的挂载点 /p/myapp/api/status + path=/p/myapp → 上游收到 /api/status 适用:自带 UI 的服务(前端用相对路径) 非法组合(strip_path 而无 path)被 ValidateProxyDef 挡住。 ## 单一入口原则(契约级要求) 一个声明 = 一个入口。两种挂载形态对「根路径」处理截然不同: Host 形态下根路径是插件的根(fetch('/api/x') 天然正确); Path 形态下根路径**属于门户**,同样代码会打到门户自己身上 (静默错路由:页面能开、功能全坏)。 故被反代的插件必须**一律使用相对路径**,绝不硬编码以 / 开头的绝对路径。 这样同一份前端在两种形态下都正确,插件不必知道自己被挂在哪, 反代层也能按外部条件(子域是否有证书/放行)自由选择形态。 ## 判据 sdk/proxy_test.go:两种语义的映射、非法组合、单一入口原则的契约存在性。 hmapdev proxy_config_test.go:schema 漂移保护(新增字段忘了同步就判红)、 非法声明在**打包时**就被拒(不必装到 HomeAgent 才看到)。 两模块 go test 全绿;文档站已重新生成(ProxyDef 与单一入口原则进入 docs/api/misc.md 与 llms-full.txt)。 ## 顺带修回的一处(此前随工作树丢失) writePluginJSON 漏写 proxies 键 —— 漏写的话插件装得上、启动正常、 就是不出现,没有任何报错。该 bug 曾在核心仓侧出现过(判据抓到过), 这次移植时由 TestWritePluginJSONPreservesProxies 再次判红并修复。 | 22 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
refactor(toolchain)!: 工具链 plugindev 更名为 hmapdev,module path 改回 gitcode - 目录 tools/plugindev → tools/hmapdev,可执行文件名/平台产物名同步 (hmapdev_linux_amd64 等;包格式仍叫 .hmap) - module path github.com/JianFeeeee/homeagent-sdk/tools/... → gitcode.com/... (与仓库实际托管一致;核心仓不依赖该 path,改动无外部影响) - SDK 存储目录 ~/.homeagent/plugindev/sdk → ~/.homeagent/hmapdev/sdk 新目录不存在而旧目录存在时沿用旧目录 → 已装 SDK 版本不会丢失 - 命令表/usage/--help/生成项目 README/示例 README/NSIS 安装器/ package/build.sh/build-examples.sh 全部同步;PLUGINDEV 环境变量保留兼容 - sdk/ 目录零改动(公开接口不变) 验证: - go build ./... ok;go test ./tools/hmapdev/ ok(含模板接线守卫 TestProcTemplate_CoversAllCoreMethods) - bash -n package/{build,build-examples}.sh ok - 端到端:hmapdev init demo && hmapdev build → dist/demo_bundle.hmap(linux+darwin) - 本机安装 /usr/local/bin/hmapdev,旧名以软链保留;sdk list/current 正常 | 1 个月前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
feat(hmapdev): 项目声明 SDK 版本,工具链据此自动选(plg.json 的 sdk 字段) 此前项目里没有任何「我要哪版 SDK」的声明:go.mod 的 require 是个 Go 模块版本, 而工具链实际用的是存储里的 current——谁改过 current 就拿谁的版本编,出错时 表现为莫名其妙的编译错误(本轮就踩过:存储里只有陈旧的 v0.8.0,模板项目 首次构建报 undefined: sdk.InjectOptions)。 - plg.json 新增 sdk 字段:本插件针对的 SDK 版本。hmapdev init 生成时写入 **完整版本号**(如 "1.2.0")。 - hmapdev build 按声明的版本在本地 SDK 存储里定位:命中则用它并把 go.mod 的 require/replace 同步到该版本;未命中则报**可执行**的错误(列出已装版本 + hmapdev sdk install vX.Y.Z),绝不静默退化成 current。 - **区间写法("1.2")被拒绝**并说明规矩:SDK 版本跟随内核中版本、patch 位恒为 .0, 一条内核线只有一个 SDK 版本(写区间会让人误以为同一条线里还能挑不同 SDK)。 - 产物 plugin.json 记录实际选中的版本(sdk),便于追溯「这个 .hmap 是哪版编的」。 - 显式 --sdk-path / plg.json sdk_path 优先(本机改 SDK 联调的路径),此路径下也尽力记录版本。 - 存量项目(plg.json 无 sdk 字段)行为不变,向后兼容。 顺带修一处自相矛盾:解析出 1.2.0 之外的版本时,原先只改 replace 而 require 保持旧版本, 一旦有人删掉 replace 就会静默用回旧 SDK 编译(go list -m 报的也是假版本)。 验证:单测 10 例(精确命中/带 v 前缀目录/区间写法被拒并说明规矩/未命中给可执行命令/ 空存储给安装指引/非法值拒绝/杂项目录不干扰)+ 反向验证(把版本比较退化成字典序, 「1.2.x 取最新」用例立刻变红,证明判据能发现缺陷)。 E2E:init → plg.json "sdk": "1.2.0";build → 精确解析、go.mod require/replace 一致、 产物 plugin.json 记录 sdk;声明不存在的版本 → 可执行报错;无 sdk 字段 → 照旧构建。 | 1 个月前 | |
feat(hmapdev): 项目声明 SDK 版本,工具链据此自动选(plg.json 的 sdk 字段) 此前项目里没有任何「我要哪版 SDK」的声明:go.mod 的 require 是个 Go 模块版本, 而工具链实际用的是存储里的 current——谁改过 current 就拿谁的版本编,出错时 表现为莫名其妙的编译错误(本轮就踩过:存储里只有陈旧的 v0.8.0,模板项目 首次构建报 undefined: sdk.InjectOptions)。 - plg.json 新增 sdk 字段:本插件针对的 SDK 版本。hmapdev init 生成时写入 **完整版本号**(如 "1.2.0")。 - hmapdev build 按声明的版本在本地 SDK 存储里定位:命中则用它并把 go.mod 的 require/replace 同步到该版本;未命中则报**可执行**的错误(列出已装版本 + hmapdev sdk install vX.Y.Z),绝不静默退化成 current。 - **区间写法("1.2")被拒绝**并说明规矩:SDK 版本跟随内核中版本、patch 位恒为 .0, 一条内核线只有一个 SDK 版本(写区间会让人误以为同一条线里还能挑不同 SDK)。 - 产物 plugin.json 记录实际选中的版本(sdk),便于追溯「这个 .hmap 是哪版编的」。 - 显式 --sdk-path / plg.json sdk_path 优先(本机改 SDK 联调的路径),此路径下也尽力记录版本。 - 存量项目(plg.json 无 sdk 字段)行为不变,向后兼容。 顺带修一处自相矛盾:解析出 1.2.0 之外的版本时,原先只改 replace 而 require 保持旧版本, 一旦有人删掉 replace 就会静默用回旧 SDK 编译(go list -m 报的也是假版本)。 验证:单测 10 例(精确命中/带 v 前缀目录/区间写法被拒并说明规矩/未命中给可执行命令/ 空存储给安装指引/非法值拒绝/杂项目录不干扰)+ 反向验证(把版本比较退化成字典序, 「1.2.x 取最新」用例立刻变红,证明判据能发现缺陷)。 E2E:init → plg.json "sdk": "1.2.0";build → 精确解析、go.mod require/replace 一致、 产物 plugin.json 记录 sdk;声明不存在的版本 → 可执行报错;无 sdk 字段 → 照旧构建。 | 1 个月前 | |
fix(hmapdev): SDK 模块识别要认新旧两个 module 名(迁移漏改的逻辑判定) 上一提交(b6a6168)只替换了 import 路径,**漏了用旧 module 名做逻辑判定 的五处**。CI 当场抓到一处( TestPrintVersionReportsInjectedVersion), 但那是测试断言;另外三处是**产品逻辑**,CI 抓不到,是静默失效。 ## 漏改的地方 | 位置 | 原判定 | 失效后果 | |---|---|---| | cmd_build.go | Contains(mod,"homeagent-sdk") | 认不出 SDK 依赖 ⇒ **不写 replace** | | cmd_skill.go | Contains(gm,"homeagent-sdk") | 认不出 SDK 仓 ⇒ 不安装 skills | | yaegi/interp.go ×2 | Contains(line,"homeagent-sdk") | findSDKGoPath 找不到 SDK ⇒ 退化到兜底 | 迁移后这些 Contains **恒为假**(新名是 homeagentsdk,没有连字符)。 三者都**不报错**,只是行为悄悄变成另一条分支——比直接崩更难发现。 ## 关键:不能只认新名 本机实测存量插件工作区的 go.mod **全部仍是旧名**: /home/program/qq-plugin (无 go.mod) /home/program/vanblog-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/mcquery-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/fileproc-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/agentmail/plugins/ require gitcode.com/JianFeeeee/homeagent-sdk 用户已创建的项目不会因为我们改仓就自动更新。所以判定必须 **两种都接受**,而不是把旧名换成新名(那只是把 bug 从"认不出新"换成"认不出旧")。 ## 改动 - meta 补 SDKModule / LegacySDKModule 常量(原注释引用了 SDKModule 但常量并不存在——注释先于实现) - 新增 tools/hmapdev/sdkmodule.go:isSDKModule / sdkModulePath / containsSDKModule - 新增 tools/hmapdev/yaegi/sdkmodule.go:同语义的最小副本 (yaegi 是独立子包,不能 import 父包) - 三处逻辑判定改用上述助手 ## 判据(可杀变异,逐个实测) sdkmodule_test.go + yaegi/sdkmodule_test.go:覆盖新名/旧名/带版本号的 旧名/空串/核心仓(HomeAgent,不是 SDK)/无关模块。 这三个判定点**此前零测试覆盖**——这正是它们能静默失效的原因。 变异验证: - 只认新名 → FAIL「旧名漏认会让存量插件静默走错分支」 - containsSDKModule 只认新名 → FAIL「必须认旧名」 ## 验证 - SDK 根 module + hmapdev module:go build / go test 全通过 - 20 个 example go vet 全通过 - go mod verify → all modules verified | 9 天前 | |
fix(hmapdev): SDK 模块识别要认新旧两个 module 名(迁移漏改的逻辑判定) 上一提交(b6a6168)只替换了 import 路径,**漏了用旧 module 名做逻辑判定 的五处**。CI 当场抓到一处( TestPrintVersionReportsInjectedVersion), 但那是测试断言;另外三处是**产品逻辑**,CI 抓不到,是静默失效。 ## 漏改的地方 | 位置 | 原判定 | 失效后果 | |---|---|---| | cmd_build.go | Contains(mod,"homeagent-sdk") | 认不出 SDK 依赖 ⇒ **不写 replace** | | cmd_skill.go | Contains(gm,"homeagent-sdk") | 认不出 SDK 仓 ⇒ 不安装 skills | | yaegi/interp.go ×2 | Contains(line,"homeagent-sdk") | findSDKGoPath 找不到 SDK ⇒ 退化到兜底 | 迁移后这些 Contains **恒为假**(新名是 homeagentsdk,没有连字符)。 三者都**不报错**,只是行为悄悄变成另一条分支——比直接崩更难发现。 ## 关键:不能只认新名 本机实测存量插件工作区的 go.mod **全部仍是旧名**: /home/program/qq-plugin (无 go.mod) /home/program/vanblog-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/mcquery-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/fileproc-plugin require gitcode.com/JianFeeeee/homeagent-sdk /home/program/agentmail/plugins/ require gitcode.com/JianFeeeee/homeagent-sdk 用户已创建的项目不会因为我们改仓就自动更新。所以判定必须 **两种都接受**,而不是把旧名换成新名(那只是把 bug 从"认不出新"换成"认不出旧")。 ## 改动 - meta 补 SDKModule / LegacySDKModule 常量(原注释引用了 SDKModule 但常量并不存在——注释先于实现) - 新增 tools/hmapdev/sdkmodule.go:isSDKModule / sdkModulePath / containsSDKModule - 新增 tools/hmapdev/yaegi/sdkmodule.go:同语义的最小副本 (yaegi 是独立子包,不能 import 父包) - 三处逻辑判定改用上述助手 ## 判据(可杀变异,逐个实测) sdkmodule_test.go + yaegi/sdkmodule_test.go:覆盖新名/旧名/带版本号的 旧名/空串/核心仓(HomeAgent,不是 SDK)/无关模块。 这三个判定点**此前零测试覆盖**——这正是它们能静默失效的原因。 变异验证: - 只认新名 → FAIL「旧名漏认会让存量插件静默走错分支」 - containsSDKModule 只认新名 → FAIL「必须认旧名」 ## 验证 - SDK 根 module + hmapdev module:go build / go test 全通过 - 20 个 example go vet 全通过 - go mod verify → all modules verified | 9 天前 | |
feat(sdk): 22 个示例插件配齐 skill + 三态启动声明 + 打包期校验 ## 用户的三条要求 1. 「所有插件都默认自启会占启动时间,还在 llms 上下文塞大量工具」 ⇒ 纯工具型改为按需加载,靠 skill 让 agent 知道要先拉起。 2. 「会向 agent 注入输入的部分插件不能这样做」 ⇒ 9 个必须常驻,其余 13 个按需。 3. 「sdk 是不是没有暴露插件声明自身行为的方案」 ⇒ 对:auto_start 只在 internal/plugin/manifest.go 有,SDK 零命中。 ## 定级依据(代码证据,不是猜描述) 注入 / 钩子 ⇒ 必须常驻(9 个): qq HTTP 监听 NapCat 推送,消息到达要立刻注入 rss 轮询订阅源,新文章主动推送 calendar 30s ticker 检查提醒 memo 5min ticker 检查待办到期 a2a TCP 监听外部 Agent 请求 acp TCP 监听 ACP 客户端 browser ticker + 长命令完成回调 sanitizer 注册 stage 钩子(无工具,要每轮生效) luademo 注册 stage 钩子做示范 纯工具型 ⇒ 按需(13 个): weather ocr music bili ai_image files editdoc deepsearch plugindev recoverydiag vanblog vikunja commondev ★ commondev 值得单独说:它虽然有 6 处 Inject 调用,但**注入源是自己的 sidecar 完成回投**,而 sidecar 只在 commondev_agent_up 时才拉起 (Start() 里不启 sidecar,通道也在 agent_up 时才注册)。所以没有 agent 调用它时,它不需要做任何事 —— 可以按需。44 个工具 ≈726 tokens 省下来可观。 ## 改动 · 22 个插件各配一个 skill,写明:何时用、先 plugin_start、 工具清单、**用 plugin_stop 而不是 plugin_disable** · 13 个 plg.json 加 auto_start: false · sdk/autostart.go:把三态语义与"按需插件必须有 skill"的理由写进公开 SDK · hmapdev 打包期校验:auto_start=false 但无 skill/ 目录 ⇒ 直接 exit 1 (理由:缺 skill 的按需插件**等价于没装**,比没装更糟 —— 占着磁盘让人以为可用) · 修 Lua 打包路径:它**完全不走** rawfile/skill 收集,luademo 的 skill 永远打不进去 · 修 build-examples.sh:原来 [ ! -x ] 判"二进制在不在",改了 hmapdev 源码后 重新打包**仍用旧工具链**(实测:luademo 的 skill 一直是 0) · 补 mocksdk.ToolDef.Timeout(上一轮加 ToolDef.Timeout 时漏了镜像) ## 判据 · TestHasSkillDirRequiresRealSkillMd(空目录/.gitkeep/无 SKILL.md 都不算) · TestBuildRejectsOnDemandWithoutSkill(打包期必须真拦住 + exit 1) · TestLuaPackagingCollectsSkillAndRawfiles(**验代码行**,不验注释 —— 第一版 只查符号存在,把 createHmapWithRaw 换回 createHmap 判据照样通过) ## 踩过的坑 · Lua 分支的结束边界原是 // Resolve build config,但那个注释在文件里 **出现多次**(Go 路径里也有),用它截取会把 Go 分支一起包进来 ⇒ 变异测不出。改成截到 Lua 分支自己的 return + }。 | 13 小时前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 | |
feat(hmapdev): 构建期告警「同一 stage 被重复注册」+ qq 合并写法 ## 用户指出的关键:告警要给**开发者**看,所以必须在构建期 我第一版把告警放进了工具链生成的 proc_main 模板(插件**运行期**打日志)。 用户直接指出:「本身工具链这个编译器警告就是给开发者看的」——运行期日志 是给运维/用户看的,开发者看不到。已撤销,改为构建期静态检查。 ## 缺陷(真实事故,2026-10-18) 插件侧把 stage handler 存在 map[string]sdk.StageHandler(stage 名为键)⇒ **同一 stage 注册两次,后者静默覆盖前者**。 QQ 插件当时写成: RegisterStage(BeforeToolcall, 权限门) ← 被下一条覆盖 RegisterStage(BeforeToolcall, 切状态) ← 实际生效 后果不只是状态不切换 —— **权限门被静默关掉了**(那几版部署里任何人 发消息都能调任何工具)。编译器不报、运行时不报错、内核日志还显示 2 handlers [qq qq](看着完全正常)。只有静态检查能发现。 ## 实现 · stage_dup_check.go:扫构建目录下的 .go(排除 _test.go 与 z_proc*), 统计每个 stage 常量被 RegisterStage 引用几次,>1 就告警 · 接入 buildTarget 开头 ⇒ 所有 target(含 lua)都检查 · **只告警不中断**:存量插件里重复注册可能是本意,直接 fail 会让它们 升 SDK 后无法构建 · example/qq:三个重复 stage 合并成单 handler(权限门 + 状态切换) ## 判据(4 条) · 同 stage 两次 ⇒ 报出来 · 不同 stage 各一次 ⇒ 不误报 · **注释里的示例注册不计入** · 测试文件 / 工具链生成文件不计入 ★ 「注释」这条是这个检查器**第一版自己栽的坑**:直接对全文跑正则, 把插件里「当初写错了、留在注释里当教训」的那几行也统计进去,于是 修好之后照样报「注册了 3 次」(实测)。现在逐行跳 // 与跨行 /* */。 本项目已多次绊倒在「注释里有、代码里没有」上,这次轮到检查器自己。 ## 实测 qq 修好后构建: packaged qq_linux_amd64.hmap(无告警) 故意加一条重复: ⚠️ 警告: stage StageBeforeOutput 注册了 2 次 建议:把多个功能合并进同一个 handler | 11 小时前 | |
feat(hmapdev): 构建期告警「同一 stage 被重复注册」+ qq 合并写法 ## 用户指出的关键:告警要给**开发者**看,所以必须在构建期 我第一版把告警放进了工具链生成的 proc_main 模板(插件**运行期**打日志)。 用户直接指出:「本身工具链这个编译器警告就是给开发者看的」——运行期日志 是给运维/用户看的,开发者看不到。已撤销,改为构建期静态检查。 ## 缺陷(真实事故,2026-10-18) 插件侧把 stage handler 存在 map[string]sdk.StageHandler(stage 名为键)⇒ **同一 stage 注册两次,后者静默覆盖前者**。 QQ 插件当时写成: RegisterStage(BeforeToolcall, 权限门) ← 被下一条覆盖 RegisterStage(BeforeToolcall, 切状态) ← 实际生效 后果不只是状态不切换 —— **权限门被静默关掉了**(那几版部署里任何人 发消息都能调任何工具)。编译器不报、运行时不报错、内核日志还显示 2 handlers [qq qq](看着完全正常)。只有静态检查能发现。 ## 实现 · stage_dup_check.go:扫构建目录下的 .go(排除 _test.go 与 z_proc*), 统计每个 stage 常量被 RegisterStage 引用几次,>1 就告警 · 接入 buildTarget 开头 ⇒ 所有 target(含 lua)都检查 · **只告警不中断**:存量插件里重复注册可能是本意,直接 fail 会让它们 升 SDK 后无法构建 · example/qq:三个重复 stage 合并成单 handler(权限门 + 状态切换) ## 判据(4 条) · 同 stage 两次 ⇒ 报出来 · 不同 stage 各一次 ⇒ 不误报 · **注释里的示例注册不计入** · 测试文件 / 工具链生成文件不计入 ★ 「注释」这条是这个检查器**第一版自己栽的坑**:直接对全文跑正则, 把插件里「当初写错了、留在注释里当教训」的那几行也统计进去,于是 修好之后照样报「注册了 3 次」(实测)。现在逐行跳 // 与跨行 /* */。 本项目已多次绊倒在「注释里有、代码里没有」上,这次轮到检查器自己。 ## 实测 qq 修好后构建: packaged qq_linux_amd64.hmap(无告警) 故意加一条重复: ⚠️ 警告: stage StageBeforeOutput 注册了 2 次 建议:把多个功能合并进同一个 handler | 11 小时前 | |
chore(module)!: module 路径随托管地迁移 gitcode → github SDK 仓已迁到 GitHub 作为权威源,go module 路径同步: gitcode.com/JianFeeeee/homeagent-sdk → github.com/JianFeeeee/homeagentsdk ## 范围 74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。 主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上** (见下),所以那次的批量替换扫不到它们——这里是补偿。 ## 为何这批文件当时不在工作树上(事故与恢复) 10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件 误清理了(example/*/plugin.go + plg.json,10 个示例)。 根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 third_party/ 里的 **独立 git 仓库**,而主仓 .gitignore 忽略了 example/,于是它在主仓 git status 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。 已全部恢复(git checkout -- <D 文件>,只恢复删除、不动迁移改动), 20 个文件与 HEAD 逐字节一致。 **破坏的连锁后果**:恢复出来的 plugin.go 仍写着旧 module 路径, 因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。 ## 保留 gitcode 引用 以下位置的 gitcode **有意保留**,不要"顺手清掉": - README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明 - mkdocs.yml 的 gitcode 镜像链接(国内直连更快) - tools/hmapdev/templates.go 里「SDK 已从 gitcode 迁到 GitHub」的注释 gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。 ## 验证 - 本仓根 module:go build ./... + go test ./... 全通过 - 全部 20 个 example:go vet 通过(补偿前 10 个因旧 module 路径失败) - example/qq:go test 通过,且能从源码 build 出可用 plugin 产物 | 9 天前 | |
feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) (#3) * feat(commondev): 主管式多仓库开发流水线 + sidecar 后端抽象(pi/zcode) ## 是什么 agent 统筹开发流程的插件:登记项目 → 开工程线(每条线一个隔离工作目录 + 一个 sidecar 子进程 + 一对通道)→ 投递任务 → 同会话续投 → 评审/合入。 38 个工具:project_*/branch_*/workdir_*/job_*/review_*/test_*/ endpoint_*/agent_*/pi_*/conflict_check。 ## sidecar 后端抽象(本轮核心) 传输层(local/ssh)与后端(pi/zcode/自定义)**正交**,四个自由组合: driver.json 描述一个后端:script / cliPath / env / args / llmConfig 三级查找:插件数据目录 → **随包 rawfile** → 内置 pi 描述 占位符:{workdir} {dataDir} {pluginDir} {rawfile} {sessionId} ★ 内置 pi 回退**与默认后端解耦**(2026-10-17 修):原实现写 if cli == defaultAgentCLI { return builtinPiDriver() },把"给谁兜底" 和"默认选谁"耦合到同一常量 —— 一旦有人改默认,pi 当场失去兜底, 显式 cli="pi" 开始报「驱动不存在」。现在判断字面 "pi"。 默认后端保持 **pi**(我们本地主用);交付方用 zcode 走两种局部开关: · 单线:commondev_branch_open(cli="zcode") · 按机器:commondev_endpoint_set(host=..., cli="zcode") ## zcode 接入(三条真机判据,全部要求真改工程文件) · TestZcodeFullPipeline(29-34s):随包 driver + 真实 ZCode CLI · TestZcodeOverSSH(39s):真 ssh + scp,远端 node 跑 zcode · TestZcodeOverSSHWithPerHostEndpoints(68s):两个 host 别名配不同 cli_path,走端点表而不是全局 settings ★ 会话库必须隔离到工作区(sidecar.mjs 的 ensureWorkspaceSessionStore): 实测 ZCode 的会话库路径**硬编码 homedir()**,ZCODE_DATA_BASE_DIR 对它 完全无效(26 个会话全落全局库)。但 <workdir>/.zcode/config.json 的 storage.sessionDbPath **确实生效**。所以启动前写一份工作区配置, 一举解决:多线不共用历史 / 不污染全局 ~/.zcode / 删线不留孤儿会话。 只在不存在时写 —— 用户手工放的要尊重。 ## 其他 · rawfile:项目根 rawfile/ 默认全收,解到 <插件目录>/rawfile/,绝对路径拒绝 · 凭证:命名引用 + ${ENV_VAR},独立 0600 文件,绝不进 config.db(0644) · secretshell:给不受信 agent 用的安全 shell(ackey + safeshell) · 冲突检查 / 评审判据 / 镜像 / 每条线独立 workdir(hash 命名,合并后删除) 判据 30+ 条,含 4 条「判据本身的判据」(防止判据假绿)。 * fix(sdk): 协议通道纪律 + hmapdev 模板升级 + 示例插件 stdout 污染 ## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道 子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。 脚手架模板(及 4 个示例插件)用 fmt.Printf 打日志 ⇒ 内核收到非 JSON 文本: [proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i' **生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。 后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。 修法: - hmapdev 模板 fmt.Printf → log.Printf(9 处) - 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3) + 补 "log" import - 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法 判别法:package main = 子进程(协议通道,禁用 stdout 打日志); 普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)—— internal/plugins/cli 的 fmt.Println 属后者。 ## hmapdev - cmd_build.go +136:平台产物精确匹配(--target 是**append** 语义, 原脚本 ls dist/*.hmap | head -1 会按字典序抓到 amd64, **导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过) - cmd_init.go +46:脚手架 stdio 纪律 - templates/proc_main.go.tmpl:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped 三态能力(插件管理器运行态停启) - templates/plg.json.tmpl 删除(合并进 cmd_init) ## 判据 - scaffold_stdout_test.go(3 条):扫模板生成的代码不得写 stdout。 ★ 判据自身先假红过一次:**模板的注释里就写了 fmt.Printf** ⇒ 必须 stripTemplateComments() 之后才判 - example_stdout_test.go(2 条):扫全部 20 个子进程示例插件, 报错给**文件:行号**;变异咬住并给出准确行号 - package_examples_test.go(2 条):**执行真实打包脚本** (第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬) - cmd_build / cmd_init 新判据 ## 其他 - sdk/settings.go +15(三态能力透传) - docs/versions.md +§示例插件协议通道纪律(含生产 1038 条实测表格) - skills/homeagent-plugin-dev/SKILL.md +§协议通道纪律(正误代码对比) * fix(examples): 恢复 go.mod 相对路径 + 移除 plg.json 的 sdk 固定(修 CI 红) CI 上 Examples (linux/amd64) 与 (darwin/arm64) 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 git add -A,把本机 go build **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 "sdk": "1.4.0"** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · TestExampleGoModHasNoAbsoluteReplace:任何示例 go.mod 不得含 /root/、/home/、/Users/、C:\ · TestExampleGoModUsesRelativeSDKReplace:replace 必须是相对路径 ../../ · TestExamplePlgJsonDoesNotPinSDKVersion:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 package/build-examples.sh: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, proxy.golang.org 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 git add -A 会把「构建工具自动改写的文件」一起提交。 提交前必须 git status 逐个看,尤其 go.mod / go.sum。 --------- Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com> | 1 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 11 小时前 | ||
| 13 小时前 | ||
| 13 小时前 | ||
| 11 小时前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 1 天前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 20 天前 | ||
| 1 天前 | ||
| 1 天前 | ||
| 9 天前 | ||
| 1 个月前 | ||
| 13 小时前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 1 天前 | ||
| 1 个月前 | ||
| 5 天前 | ||
| 22 天前 | ||
| 22 天前 | ||
| 1 天前 | ||
| 1 天前 | ||
| 1 个月前 | ||
| 1 天前 | ||
| 1 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 13 小时前 | ||
| 1 天前 | ||
| 11 小时前 | ||
| 11 小时前 | ||
| 9 天前 | ||
| 1 天前 |