From a68d6a428c8a1b7685f1f6bb5be1c35e4eb1e0c5 Mon Sep 17 00:00:00 2001 From: JianFeeeee <109188060+JianFeeeee@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:18:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A8=A1=E5=9D=97=E5=8C=96=E5=86=85?= =?UTF-8?q?=E6=A0=B8=EF=BC=88=E7=BC=96=E8=AF=91=E6=9C=9F=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E5=9B=BE=E5=AE=9A=E5=BA=8F=EF=BC=89+=20=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E9=98=B2=E7=81=AB=E5=A2=99=20+=20=E6=8F=92=E4=BB=B6=E4=B8=89?= =?UTF-8?q?=E6=80=81=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(kernel): 模块化内核 + 编译期依赖图定序 把单体 init 序列拆成「模块自声明依赖 + 编译期生成依赖图 + 运行期只读图」。 ## 为什么 原 cmd/homed/main.go 里 17 步 init 是**手写顺序**,依赖关系只存在于 注释和人脑里。实测踩到的坑(见 docs/zh/kernel-modularization-design.md): · nlp.defaultParser 被 initONNXParser 写、被 distiller 读,两者相距 60 行, 只靠 ticker 10min 才没炸(隐式依赖,无声明) · 加一个模块要人肉找"该插在哪",插错的表现是运行期 nil 解引用 · 关停是 defer ks.CloseAll(),而热更新路径 os.Exit(42) **跳过 defer** ⇒ docStore/distiller/textMem 的 flush 全部丢失(每次热更新都发生) ## 怎么改 - internal/kernelboot/:KernelState(一个结构体收全部包级共享变量,字段 nil 安全)、Module 接口、graphfile(依赖图编解码 + 与二进制 hash 对账)、 scheduler(按 BFS 层并行 Init)、syncbus(层边界写通知)、 lifecycle(Quiesce/Resume 可逆伪关机,区别于一次性 CloseAll) - internal/kernelmod//:每个模块一个文件夹,Requires/Provides 自声明; all/all.go 是唯一的 import 清单(Go 循环导入约束) - cmd/homed-depgraph:编译期生成依赖图,Makefile 注入 digest - 运行期只**读**图,不计算顺序(顺序是编译期决定的,源码里没有中央清单) ## 判据 kernelboot 8 条 + 图失败模式 7 条(全部拒绝并给准确错误)+ 模块目录数 与注册表一致性。变异测试 15/15 咬住,含: · 删掉 AddCloser 的锁 → -race 报数据竞争 · Scheduler 同批跑去重 → 203 vs 200 · 图 hash 不匹配 → 拒绝启动 ## 顺带修的缺陷 热更新路径漏调模块关闭 ⇒ 显式 stopKernelModules(),并用 AST 判据钉住 "必须在 os.Exit 之前"(字符串检查在本仓已骗过我三次)。 实测:冷启动 1630ms(5 层/17 模块,原 17 步串行);生产 22 插件全部建链。 * feat(security): 密钥防火墙 + 配置加密 + close 纪律 ## 密钥防火墙 模型只见别名、不见明文;插件工具调用入口(StageHost.ExecuteTool)做入向 回填。三维能力: · 出向:工具输出里的密钥被替换成 [[secret:name]] 别名后才进上下文 · 入向:模型传别名,入口处填回真值(插件完全无感知) · 存储:config.db 里不存明文(0644 的库可读),敏感值独立 0600 文件 模式库「宁漏勿误伤」:宁可漏掉一次替换,也不把正常文本误当密钥。 ## 配置加密 - internal/security/{codec,key,keyring,store,global}.go:AES-256-GCM, enc:v1: 前缀,密钥轮换(OldKeys 解码 + 新钥加密) - harden.go:数据目录权限收紧 - embedded.go:支持内嵌主密钥(可选,仍保留外部 key 文件路径) - EncryptExistingSecrets / AuditSecrets:启动时幂等迁移 + 审计 ## close 纪律(用户明确要求:只要调 close 就要有前置检查) internal/safeclose:Safe / SafeFunc。全仓 37 处裸 close(chan) → 0。 关键教训(都写进注释和判据): · `select { case <-ch: default: close(ch) }` **不是**并发安全的 (两个 goroutine 可同时命中 default)—— healthcheck 原先是这种写法 · `sync.Once` **不可重入**:X.Do(func(){ Safe(&X, ...) }) 会自锁, 症状是 go test 直接挂住(3 个包 600s 超时),栈里只有嵌套的 Once.Do · map 里按请求持有的 channel(mcp/stdio.go、clawhubadapter/sidecar.go) 改成 *pendingRPC 包装,每通道一个 once ## 判据 security 全量 + TestNoBareCloseOutsideSafeclose(AST 级扫描,含注释剥离) + TestNoNestedSelfOnceDeadlock(正则修正:\w+ 不匹配点号,捕获组要含 '.')。 变异 2/2 咬住。全量 52 包 ok。 * feat(plugin+deploy): 插件三态生命周期、数据目录迁移、发行包修复 ## 插件三态生命周期(用户要求:自动启动 / 启用但不自动启动 / 禁用) - Manifest 新增 requires_facilities / auto_start(*bool,用于区分 "未声明"与"显式 false") - plugin-host 模块(internal/kernelmod/pluginhost):用 kernelboot.Scheduler 按依赖时机拉起,**不再手写两阶段** - 运行态停启 StopPlugin/StartPlugin **可逆**:不写 disabled_plugins (不改用户决定)、不置 shuttingDown(之后还能加载)、会摘掉注册面 - 与 StopAll 的区别写进注释 —— StopAll 置 shuttingDown 后**永久无法加载**, 那是进程退出用的,误用于"暂停"就是关了就死 ## 数据目录迁移(伪关机 → 迁移 → 唤醒) - kernelboot/lifecycle.go:Quiesce/Resume(可逆,区别于一次性 CloseAll), 逆序暂停、逆序恢复、Pause 失败回滚 - kernelboot/migrate.go:BeginMigration/End 编排(先停插件 → 再停内核写者 → 等在途归零) - distiller/textmem 增 Pause/Resume 门(不是 cancel —— cancel 不可逆, 恢复要重建整个对象) - 模块 migration:体检 + 生成可执行迁移脚本 ★ 迁移脚本的三个实测教训(都写成了判据): 1. 无条件改 /etc/systemd/system ⇒ 演练把**生产** -data 改成了演练目录。 加 --dry-run(零改动)+ 强制校验 systemd 当前 -data == OLD 2. 本机没装 rsync,脚本在"服务已停、数据未搬"处硬失败 ⇒ cp -a 回退, 并明确警告不保硬链接/ACL(生产 plugindev 有大量 git 硬链接) ## 发行包修复(全部经实测确认) 1. package-linux.sh **完全漏打 depgraph.bin** ⇒ 内核 exit 1 ("生产构建(内嵌 hash 非空)但读不到依赖图") 2. build_homed 不走 Makefile ⇒ 单独修 Makefile 无效,必须同时生成图+注入 3. build.sh 的 LDFLAGS 用**历史模块路径 gitcode.com** ⇒ -X 静默 no-op ⇒ --version 显示 unknown 4. Makefile -ldflags 用**单引号** ⇒ 注入了字面 $GRAPH_HASH ## ★ build.sh 的版本号必须校验(真实事故) 原 `VERSION="$(git describe --tags --dirty)"`。无 tag 分支上它返回**分支名** `backup/pre-identity-rewrite-20261005-51-g03d87a4e-dirty`,被注入 meta.Version ⇒ 插件握手判据 MajorVersion() 把整串当主版本 ⇒ **22 个子进程插件全部 握手失败**("主版本不匹配")。内核活着、systemd active、日志每条都像 插件的问题 —— 实际是发行包带着非法版本号。 修法:git describe 只在形如 vX.Y.Z 时采用,否则回退 meta.Version(权威源), 并对最终值做形态守卫。deploy/packaging/version_guard_test.go 3 条判据, 变异 2/2 咬住。 ## deb 的 systemd unit 改为 guard 模式 ExecStart=/usr/bin/homed --role=guard --data /var/lib/homeagent (worker exit 42 → guard 重拉,不经过 systemd、不计失败) 含 RestartSteps/RestartMaxDelaySec/TimeoutStopSec + 硬化 (NoNewPrivileges/ProtectSystem/ProtectHome/DevicePolicy/ReadWritePaths)。 ## 其他 - internal/plugin/proc:非法 JSON 帧的报错**可定位**(内容摘要 + 可能原因 + 修法)。生产实测 1038 条非法帧(插件往 stdout 打日志 = 污染协议通道) - 示例插件 4 个 14 处 fmt.Printf → log.Printf - hmapdev 模板同步修复;scaffold 判据剥离注释(模板注释里就含 fmt.Printf) * chore: 收尾(主密钥轮换命令、IPC/知识库加固、SDK 对齐) - cmd/homed-key-rotate:把 security 里的 RotateKey/RotateKeyFile/RekeySecrets 串成一条**可执行**的运维路径。没有它,E-1 就只是库里的能力而不是 "出事时能做的事",而轮换的价值恰恰在于能执行。 - cmd/homed:kmod_runtime(模块装配入口)、plugins_scheduled(failback 白名单)、memcandidate、bootstrap_order_test / shutdown_flush_test - internal/ipc、internal/devicebridge、internal/knowledge:配套加固 - third_party/homeagent-sdk/sdk:PluginMgrAPI 增三态能力(mgrStop/mgrStart/ mgrIsRuntimeStopped)+ settings 对齐 判据:bootstrap_order_test(启动顺序不依赖手写序列)、 shutdown_flush_test(停机的两条路径必须对称 —— exit 42 会跳过 defer, 热更新路径下模块关闭/flush 曾全部丢失)。 * test(security): 测试夹具改用不可误认的占位值(修 push protection 拦截) GitHub secret scanning 拦住了推送:三处测试夹具用了"看起来像真密钥"的 字面量: internal/security/prefilter_test.go:39 internal/security/store_test.go:177 internal/security/survey_test.go:20 它们全是**假密钥**(`AKIAIOSFODNN7EXAMPLE` 是 AWS 官方文档的标准示例, 其余是 `abcdefghij...` 这类可读占位符),但扫描器无法区分 —— 它只看形态。 ★ 不绕过(不用 unblock URL、不加 allowlist):直接改夹具。 原因:一旦允许"假密钥可以通过",真密钥混进来时就没有最后一道门了。 改法:保留**模式库匹配所需的结构**,把可识别内容换成 FAKE: glpat-abcdefghijklmnopqrst → glpat-FAKE-TEST-TOKEN-NOT-REAL sk-abcdefghij...012345 → sk-FAKE-TEST-KEY-NOT-REAL-0000000 AKIAIOSFODNN7EXAMPLE → AKIA0000FAKE0000TEST (保持 20 字符) ASIAIOSFODNN7EXAMPLE → ASIA0000FAKE0000TEST xoxb-123456789012-abcdefghijklmnop → xoxb-FAKE-TEST-TOKEN-NOT-REAL ★ 中途踩到一次:AWS 键的模式是 `\b(AKIA|ASIA)[0-9A-Z]{16}\b`(共 20 字符), 我第一次替换成 19 字符的 `AKIAFAKETESTKEY0000` ⇒ 模式匹配不上 ⇒ TestSurvey_WhatIsCaught / TestRecognizesCommonShapes / TestPrefilterNeverBlocksRealMatches 三条当场变红。 判据正确地拒绝了"为了过扫描器而削弱覆盖"。 顺带核实:全仓 grep 无任何真实密钥泄漏。 * fix(packaging): package-linux.sh 的版本解析与 build.sh 统一 上一版只修了 build.sh,漏了 package-linux.sh —— 它仍只看 git describe: VERSION="${VERSION:-$(git describe --tags --dirty)}" 无 tag 分支上 git describe 返回**分支名** (backup/pre-identity-rewrite-20261005-56-g98850e1),守卫会正确拒绝, 但要求人**手动传 VERSION** 才能打包。而 build.sh 早已能自动回退 meta.Version ⇒ 同一个版本,走 build.sh 打得出来、走 package-linux.sh 打不出来。 危害不只是"要手动传参":运维为了跑通会去掉守卫,而分支名一旦进 meta.Version 就是**插件协议主版本**的来源 ⇒ 22 个子进程插件全部握手失败 (这是真实发生过的事故,见 3efa4e5)。 修法:两个脚本用同一套规则(git describe 仅在 vX.Y.Z 形态时采用, 否则回退 meta.Version),并对最终值做形态守卫。 判据:TestBothPackagingScriptsShareVersionRules —— 逐个读两个脚本, 要求都能从 meta/meta.go 取权威版本、都校验 git describe 形态。 变异(把 package-linux.sh 改回旧规则)咬住并准确指出是哪个脚本。 实测:不传 VERSION 跑 package-linux.sh,输出 [PKG] warn: git describe 给出非版本串(...),改用 meta.Version=1.4.0 Version: 1.4.0 * fix(test): 跨仓判据在 CI 上应 skip 而非 fail(修 PR 双红) CI 上 "Go build / vet / test" 与 "Build linux/amd64" 双红: --- FAIL: TestHmapdevTemplateImplementsAllPluginMgrMethods bridge_sync_test.go:170: 读模板: open ../../../third_party/homeagent-sdk/tools/hmapdev/templates/proc_main.go.tmpl: no such file or directory ## 根因:判据依赖了一个**被 gitignore 的路径** `.gitignore:33` 有 `third_party/homeagent-sdk/tools/`(SDK 仓自治,本仓不跟踪)。 于是: · 本机(完整 SDK 检出)有该文件 ⇒ 判据通过 · CI(只 checkout 本仓)没有该文件 ⇒ Fatalf ⇒ **必红** 我提交时只在本机跑测试、没看 CI,所以这个问题拖到 PR 才暴露。 ## 修法 文件缺失时 `t.Skipf` 而不是 `t.Fatalf`。 ★ 这不是"放弃检查": · 跨仓一致性由 **SDK 仓自己的判据**守着 (tools/hmapdev/scaffold_stdout_test.go 等),那里才是模板的真实归属地 · 本判据在本机开发时**仍然生效**(已验证:有文件时 PASS,缺文件时 SKIP) ## 验证 · 本机(有文件) → PASS(判据真跑) · 模拟 CI(移走 tools/)→ SKIP(不再 fail) · 全量 53 包 ok / 0 fail ## 顺带 同目录还有 e2e_template_test.go 也读 tools/,但它是 `//go:build` 门控的 e2e 判据,CI 默认不跑,无需处理。 --------- Co-authored-by: JianFeeeee --- Makefile | 36 +- assets/skills/commondev-discipline/SKILL.md | 871 +++++++++++ assets/skills/commondev-discipline/skill.json | 6 + cmd/homed-centroid/main.go | 7 +- cmd/homed-depgraph/main.go | 142 ++ cmd/homed-key-rotate/main.go | 135 ++ cmd/homed-media-backfill/main.go | 5 +- cmd/homed/bootstrap.go | 721 +-------- cmd/homed/bootstrap_order_test.go | 126 ++ cmd/homed/bootstrap_test.go | 42 - cmd/homed/failback.go | 4 +- cmd/homed/guard.go | 47 +- cmd/homed/guard_config_test.go | 18 +- cmd/homed/kmod_runtime.go | 188 +++ cmd/homed/main.go | 269 ++-- cmd/homed/memcandidate.go | 79 + cmd/homed/plugins_scheduled.go | 80 + cmd/homed/shutdown_flush_test.go | 125 ++ cmd/homed/shutdown_idempotent_test.go | 113 ++ cmd/mock-server/main.go | 24 +- cmd/mock-server/ws.go | 2 +- cmd/waiter/daemon.go | 4 +- cmd/waiter/main.go | 4 +- cmd/waiter/raw_windows.go | 10 +- cmd/waiter/state.go | 4 +- deploy/depgraph-decls.json | 18 + deploy/homeagent.service | 91 +- deploy/packaging/build.sh | 78 +- deploy/packaging/linux/deb/control-toolchain | 22 + deploy/packaging/package-linux.sh | 123 +- deploy/packaging/packaging_test.go | 176 +++ deploy/packaging/version_guard_test.go | 147 ++ deploy/scripts/deploy-plan.sh | 19 + docs/zh/commondev-agent-runtime-design.md | 896 +++++++++++ docs/zh/commondev-design.md | 639 +++++++- docs/zh/kernel-modularization-design.md | 1339 +++++++++++++++++ docs/zh/kernel-secret-firewall-design.md | 908 +++++++++++ docs/zh/resident-subagent-design.md | 21 + internal/agent/api/codec_cgo.go | 26 +- .../agent/api/codec_chunkfast_bench_test.go | 17 +- internal/agent/api/codec_chunkfast_c.go | 1 - .../agent/api/codec_chunkfast_golden_test.go | 8 +- .../agent/api/codec_estimate_calib_test.go | 4 +- internal/agent/api/codec_golden_test.go | 1 - internal/agent/api/codec_jsongolden_test.go | 19 +- internal/agent/api/codec_jsonscan_cgo.go | 16 +- internal/agent/api/codec_pure.go | 3 +- internal/agent/api/codec_streamchunk_c.go | 19 +- internal/agent/api/provider.go | 29 +- .../agent/api/provider_error_class_test.go | 2 +- internal/agent/api/provider_security.go | 99 ++ internal/agent/api/security_premise_test.go | 341 +++++ internal/agent/api/security_wiring_test.go | 193 +++ internal/agent/core/agent.go | 50 +- internal/agent/core/comefrom.go | 96 ++ internal/agent/core/comefrom_test.go | 165 ++ internal/agent/core/commondev_premise_test.go | 229 +++ internal/agent/core/context.go | 8 + internal/agent/core/context_tuning_test.go | 1 + internal/agent/core/crossmodal.go | 10 +- internal/agent/core/distill.go | 8 +- internal/agent/core/encshell_premise_test.go | 54 + .../agent/core/encshell_redundancy_test.go | 154 ++ internal/agent/core/firewall_e2e_test.go | 284 ++++ internal/agent/core/inplace_scrub_test.go | 72 + internal/agent/core/leakback_test.go | 119 ++ internal/agent/core/medialoop.go | 4 +- internal/agent/core/memory_scrub.go | 158 ++ internal/agent/core/output.go | 16 + internal/agent/core/overflow.go | 4 +- internal/agent/core/overflow_test.go | 5 +- internal/agent/core/persist_leak_test.go | 221 +++ internal/agent/core/plugins.go | 2 + internal/agent/core/secret_tools.go | 154 ++ internal/agent/core/secret_tools_test.go | 191 +++ .../agent/core/security_concurrency_test.go | 287 ++++ internal/agent/core/stage.go | 7 +- internal/agent/core/stages.go | 18 +- internal/agent/core/task.go | 11 + internal/agent/core/toolcall.go | 2 + internal/agent/core/tooldefs.go | 11 + internal/agent/core/toolresult_scrub_test.go | 222 +++ internal/agent/io/channel_test.go | 18 +- internal/config/backup_diag_test.go | 93 ++ internal/config/registry.go | 157 +- internal/config/secret.go | 408 +++++ internal/config/secret_test.go | 416 +++++ internal/devicebridge/client/bridge.go | 13 +- internal/ipc/ipc.go | 31 +- internal/kernelboot/funcmodule.go | 77 + internal/kernelboot/graphfile.go | 443 ++++++ internal/kernelboot/graphfile_test.go | 291 ++++ internal/kernelboot/kernelboot.go | 402 +++++ internal/kernelboot/kernelboot_test.go | 403 +++++ internal/kernelboot/kernelstate.go | 327 ++++ internal/kernelboot/kernelstate_test.go | 110 ++ internal/kernelboot/lifecycle.go | 123 ++ internal/kernelboot/makefile_inject_test.go | 123 ++ internal/kernelboot/migrate.go | 182 +++ internal/kernelboot/migrate_test.go | 219 +++ internal/kernelboot/modules/registry.go | 94 ++ internal/kernelboot/plaingraph_test.go | 167 ++ internal/kernelboot/scheduler.go | 309 ++++ internal/kernelboot/stress_test.go | 319 ++++ internal/kernelboot/syncbus.go | 290 ++++ internal/kernelboot/syncbus_test.go | 412 +++++ internal/kernelboot/unknownfac_test.go | 139 ++ internal/kernelmod/agent/mod.go | 316 ++++ internal/kernelmod/all/all.go | 43 + .../kernelmod/all/facilities_sync_test.go | 112 ++ internal/kernelmod/all/registry_test.go | 351 +++++ internal/kernelmod/config/mod.go | 43 + internal/kernelmod/core.go | 141 ++ internal/kernelmod/docstore/mod.go | 37 + internal/kernelmod/facilities.go | 46 + internal/kernelmod/kernelapi/mod.go | 35 + internal/kernelmod/knowledge/migrate_impl.go | 53 + internal/kernelmod/knowledge/migration.go | 36 + internal/kernelmod/knowledge/store.go | 33 + internal/kernelmod/llm/mod.go | 89 ++ internal/kernelmod/lua/mod.go | 34 + .../kernelmod/memory/distiller_wiring_test.go | 85 ++ internal/kernelmod/memory/mod.go | 149 ++ internal/kernelmod/memory/nilcheck_test.go | 71 + internal/kernelmod/migration/mod.go | 284 ++++ internal/kernelmod/migration/mod_test.go | 171 +++ internal/kernelmod/multimodal/mod.go | 70 + internal/kernelmod/onnx/mod.go | 47 + internal/kernelmod/pluginhost/catalog.go | 194 +++ internal/kernelmod/pluginhost/catalog_test.go | 446 ++++++ .../kernelmod/pluginhost/migrate_adapter.go | 60 + internal/kernelmod/pluginhost/mod.go | 352 +++++ internal/kernelmod/pluginhost/rwmutex.go | 17 + internal/kernelmod/stagehost/mod.go | 97 ++ internal/kernelmod/supervisor/mod.go | 32 + internal/kernelmod/textmem/mod.go | 61 + internal/kernelmod/tracker/mod.go | 38 + internal/knowledge/knowledge.go | 1 - internal/log/compressor.go | 2 +- internal/log/manager.go | 16 +- internal/log/retention.go | 2 - internal/memory/block.go | 19 +- internal/memory/migrate_blockdata_test.go | 5 +- internal/memory/pipeline/pipeline.go | 85 ++ internal/memory/prod_copy_regression_test.go | 9 +- internal/memory/sentence_block.go | 1 + internal/memory/stress_block_union_test.go | 11 +- internal/memory/text/text.go | 59 +- internal/nlp/model.go | 8 +- internal/nlp/parser.go | 17 +- internal/plugin/dynamic_proc.go | 14 +- internal/plugin/manifest.go | 116 ++ internal/plugin/manifest_timeout_test.go | 168 +++ internal/plugin/proc/bridge_sync_test.go | 242 +++ internal/plugin/proc/capability.go | 18 +- internal/plugin/proc/capability_test.go | 71 +- internal/plugin/proc/corehandler.go | 23 +- internal/plugin/proc/corehandler_runtime.go | 36 + internal/plugin/proc/corehandler_secrets.go | 119 ++ internal/plugin/proc/corehandler_settings.go | 39 + internal/plugin/proc/e2e_secret_test.go | 138 ++ internal/plugin/proc/e2e_secrets_test.go | 217 +++ internal/plugin/proc/evtring.go | 6 +- internal/plugin/proc/exit_order_test.go | 5 +- internal/plugin/proc/extra_bag_test.go | 41 + .../plugin/proc/handshake_timeout_test.go | 199 +++ internal/plugin/proc/ipc_floor_test.go | 7 +- internal/plugin/proc/lock.go | 4 +- internal/plugin/proc/plugin.go | 54 +- internal/plugin/proc/plugin_test.go | 9 +- internal/plugin/proc/process.go | 97 +- internal/plugin/proc/process_test.go | 71 + internal/plugin/proc/protocol.go | 24 + internal/plugin/proc/shmcodec.go | 1 - internal/plugin/proc/supervisor.go | 4 +- .../plugin/proc/testdata/slowshakeplugin.go | 77 + internal/plugin/proc_core.go | 8 + internal/plugin/registry.go | 336 ++++- internal/plugin/runtime_state_test.go | 308 ++++ internal/plugins/agentcli/plugin.go | 14 +- internal/plugins/agentcli/plugin_stub.go | 4 +- internal/plugins/agentcli/pty_windows_test.go | 10 +- internal/plugins/clawhubadapter/plugin.go | 84 +- internal/plugins/clawhubadapter/sidecar.go | 51 +- .../plugins/clawhubadapter/sidecar_test.go | 42 +- internal/plugins/deepsearch_e2e_test.go | 9 +- internal/plugins/healthcheck/plugin.go | 13 +- internal/plugins/healthcheck/plugin_test.go | 4 +- internal/plugins/kbtree/plugin.go | 2 +- internal/plugins/mcp/plugin.go | 4 +- internal/plugins/mcp/stdio.go | 38 +- .../plugins/pluginmgr/lifecycle_tools_test.go | 153 ++ .../plugins/pluginmgr/package_limit_test.go | 325 ++++ internal/plugins/pluginmgr/plugin.go | 388 ++++- .../plugins/pluginmgr/rawfile_install_test.go | 157 ++ .../pluginmgr/rawfile_realinstall_test.go | 101 ++ internal/plugins/pluginmgr/skill_handout.go | 182 +++ .../plugins/pluginmgr/skill_handout_test.go | 385 +++++ .../pluginmgr/skill_realinstall_test.go | 69 + internal/plugins/pluginmgr/upgrade_test.go | 17 +- internal/plugins/seemedia/integration_test.go | 3 +- internal/plugins/seemedia/plugin.go | 3 +- internal/plugins/skillmgr/plugin.go | 2 +- internal/plugins/skillmgr/skillmgr_test.go | 51 +- internal/plugins/skillmgr/tools.go | 2 +- internal/plugins/timer/plugin.go | 5 +- internal/plugins/webui/dashboard.js | 57 + internal/plugins/webui/handler.go | 32 +- internal/plugins/webui/handler_agents.go | 29 +- internal/plugins/webui/handler_chat.go | 21 +- .../plugins/webui/handler_graph_pulse_test.go | 4 +- .../plugins/webui/handler_knowledge_test.go | 8 +- internal/plugins/webui/handler_openai.go | 4 +- internal/plugins/webui/handler_plugin_test.go | 7 + internal/plugins/webui/handler_settings.go | 21 + internal/plugins/webui/secret_meta_test.go | 117 ++ internal/plugins/webui/settings_secret.go | 150 ++ .../plugins/webui/settings_secret_test.go | 316 ++++ internal/recovery/recovery.go | 29 +- internal/recovery/recovery_test.go | 6 +- internal/safeclose/close_discipline_test.go | 274 ++++ internal/safeclose/export_test.go | 7 + internal/safeclose/safeclose.go | 83 + internal/sdk/e2e_embedded_pi_test.go | 76 + internal/sdk/memory.go | 5 +- internal/sdk/plugin.go | 13 + internal/sdk/rawfiles_impl.go | 138 ++ internal/sdk/rawfiles_impl_test.go | 166 ++ internal/sdk/secrets_impl.go | 111 ++ internal/sdk/settings.go | 5 + internal/sdk/settings_impl.go | 37 + internal/sdk/supervisor.go | 2 + internal/security/alias_shape_test.go | 24 + internal/security/bench_test.go | 143 ++ internal/security/codec.go | 285 ++++ internal/security/codec_rotation_test.go | 51 + internal/security/embedded.go | 144 ++ internal/security/embedded_test.go | 269 ++++ internal/security/global.go | 101 ++ internal/security/harden.go | 168 +++ internal/security/key.go | 198 +++ internal/security/key_test.go | 253 ++++ internal/security/keyring.go | 138 ++ internal/security/patterns.go | 135 ++ internal/security/prefilter_test.go | 210 +++ internal/security/rotation_test.go | 184 +++ internal/security/ssh_fp_test.go | 50 + internal/security/ssh_surface_test.go | 62 + internal/security/ssh_test.go | 70 + internal/security/store.go | 667 ++++++++ internal/security/store_test.go | 443 ++++++ internal/security/stress_test.go | 304 ++++ internal/security/survey_test.go | 81 + internal/supervisor/daemon.go | 33 + internal/supervisor/sdk_adapter.go | 4 + internal/system/guard.go | 2 +- internal/system/network.go | 2 +- internal/system/system_test.go | 14 +- internal/systemd/migrate.go | 316 ++++ internal/systemd/migrate_test.go | 178 +++ internal/systemd/sync.go | 244 +++ third_party/homeagent-sdk/sdk/facilities.go | 77 + third_party/homeagent-sdk/sdk/plugin.go | 65 +- third_party/homeagent-sdk/sdk/rawfiles.go | 56 + third_party/homeagent-sdk/sdk/secrets.go | 111 ++ third_party/homeagent-sdk/sdk/settings.go | 15 + 266 files changed, 30185 insertions(+), 1424 deletions(-) create mode 100644 assets/skills/commondev-discipline/SKILL.md create mode 100644 assets/skills/commondev-discipline/skill.json create mode 100644 cmd/homed-depgraph/main.go create mode 100644 cmd/homed-key-rotate/main.go create mode 100644 cmd/homed/bootstrap_order_test.go delete mode 100644 cmd/homed/bootstrap_test.go create mode 100644 cmd/homed/kmod_runtime.go create mode 100644 cmd/homed/memcandidate.go create mode 100644 cmd/homed/plugins_scheduled.go create mode 100644 cmd/homed/shutdown_flush_test.go create mode 100644 cmd/homed/shutdown_idempotent_test.go create mode 100644 deploy/depgraph-decls.json create mode 100644 deploy/packaging/linux/deb/control-toolchain create mode 100644 deploy/packaging/packaging_test.go create mode 100644 deploy/packaging/version_guard_test.go create mode 100644 docs/zh/commondev-agent-runtime-design.md create mode 100644 docs/zh/kernel-modularization-design.md create mode 100644 docs/zh/kernel-secret-firewall-design.md create mode 100644 internal/agent/api/provider_security.go create mode 100644 internal/agent/api/security_premise_test.go create mode 100644 internal/agent/api/security_wiring_test.go create mode 100644 internal/agent/core/comefrom.go create mode 100644 internal/agent/core/comefrom_test.go create mode 100644 internal/agent/core/commondev_premise_test.go create mode 100644 internal/agent/core/encshell_premise_test.go create mode 100644 internal/agent/core/encshell_redundancy_test.go create mode 100644 internal/agent/core/firewall_e2e_test.go create mode 100644 internal/agent/core/inplace_scrub_test.go create mode 100644 internal/agent/core/leakback_test.go create mode 100644 internal/agent/core/memory_scrub.go create mode 100644 internal/agent/core/persist_leak_test.go create mode 100644 internal/agent/core/secret_tools.go create mode 100644 internal/agent/core/secret_tools_test.go create mode 100644 internal/agent/core/security_concurrency_test.go create mode 100644 internal/agent/core/toolresult_scrub_test.go create mode 100644 internal/config/backup_diag_test.go create mode 100644 internal/config/secret.go create mode 100644 internal/config/secret_test.go create mode 100644 internal/kernelboot/funcmodule.go create mode 100644 internal/kernelboot/graphfile.go create mode 100644 internal/kernelboot/graphfile_test.go create mode 100644 internal/kernelboot/kernelboot.go create mode 100644 internal/kernelboot/kernelboot_test.go create mode 100644 internal/kernelboot/kernelstate.go create mode 100644 internal/kernelboot/kernelstate_test.go create mode 100644 internal/kernelboot/lifecycle.go create mode 100644 internal/kernelboot/makefile_inject_test.go create mode 100644 internal/kernelboot/migrate.go create mode 100644 internal/kernelboot/migrate_test.go create mode 100644 internal/kernelboot/modules/registry.go create mode 100644 internal/kernelboot/plaingraph_test.go create mode 100644 internal/kernelboot/scheduler.go create mode 100644 internal/kernelboot/stress_test.go create mode 100644 internal/kernelboot/syncbus.go create mode 100644 internal/kernelboot/syncbus_test.go create mode 100644 internal/kernelboot/unknownfac_test.go create mode 100644 internal/kernelmod/agent/mod.go create mode 100644 internal/kernelmod/all/all.go create mode 100644 internal/kernelmod/all/facilities_sync_test.go create mode 100644 internal/kernelmod/all/registry_test.go create mode 100644 internal/kernelmod/config/mod.go create mode 100644 internal/kernelmod/core.go create mode 100644 internal/kernelmod/docstore/mod.go create mode 100644 internal/kernelmod/facilities.go create mode 100644 internal/kernelmod/kernelapi/mod.go create mode 100644 internal/kernelmod/knowledge/migrate_impl.go create mode 100644 internal/kernelmod/knowledge/migration.go create mode 100644 internal/kernelmod/knowledge/store.go create mode 100644 internal/kernelmod/llm/mod.go create mode 100644 internal/kernelmod/lua/mod.go create mode 100644 internal/kernelmod/memory/distiller_wiring_test.go create mode 100644 internal/kernelmod/memory/mod.go create mode 100644 internal/kernelmod/memory/nilcheck_test.go create mode 100644 internal/kernelmod/migration/mod.go create mode 100644 internal/kernelmod/migration/mod_test.go create mode 100644 internal/kernelmod/multimodal/mod.go create mode 100644 internal/kernelmod/onnx/mod.go create mode 100644 internal/kernelmod/pluginhost/catalog.go create mode 100644 internal/kernelmod/pluginhost/catalog_test.go create mode 100644 internal/kernelmod/pluginhost/migrate_adapter.go create mode 100644 internal/kernelmod/pluginhost/mod.go create mode 100644 internal/kernelmod/pluginhost/rwmutex.go create mode 100644 internal/kernelmod/stagehost/mod.go create mode 100644 internal/kernelmod/supervisor/mod.go create mode 100644 internal/kernelmod/textmem/mod.go create mode 100644 internal/kernelmod/tracker/mod.go create mode 100644 internal/plugin/manifest_timeout_test.go create mode 100644 internal/plugin/proc/bridge_sync_test.go create mode 100644 internal/plugin/proc/corehandler_secrets.go create mode 100644 internal/plugin/proc/e2e_secret_test.go create mode 100644 internal/plugin/proc/e2e_secrets_test.go create mode 100644 internal/plugin/proc/handshake_timeout_test.go create mode 100644 internal/plugin/proc/testdata/slowshakeplugin.go create mode 100644 internal/plugin/runtime_state_test.go create mode 100644 internal/plugins/pluginmgr/lifecycle_tools_test.go create mode 100644 internal/plugins/pluginmgr/package_limit_test.go create mode 100644 internal/plugins/pluginmgr/rawfile_install_test.go create mode 100644 internal/plugins/pluginmgr/rawfile_realinstall_test.go create mode 100644 internal/plugins/pluginmgr/skill_handout.go create mode 100644 internal/plugins/pluginmgr/skill_handout_test.go create mode 100644 internal/plugins/pluginmgr/skill_realinstall_test.go create mode 100644 internal/plugins/webui/secret_meta_test.go create mode 100644 internal/plugins/webui/settings_secret.go create mode 100644 internal/plugins/webui/settings_secret_test.go create mode 100644 internal/safeclose/close_discipline_test.go create mode 100644 internal/safeclose/export_test.go create mode 100644 internal/safeclose/safeclose.go create mode 100644 internal/sdk/e2e_embedded_pi_test.go create mode 100644 internal/sdk/rawfiles_impl.go create mode 100644 internal/sdk/rawfiles_impl_test.go create mode 100644 internal/sdk/secrets_impl.go create mode 100644 internal/security/alias_shape_test.go create mode 100644 internal/security/bench_test.go create mode 100644 internal/security/codec.go create mode 100644 internal/security/codec_rotation_test.go create mode 100644 internal/security/embedded.go create mode 100644 internal/security/embedded_test.go create mode 100644 internal/security/global.go create mode 100644 internal/security/harden.go create mode 100644 internal/security/key.go create mode 100644 internal/security/key_test.go create mode 100644 internal/security/keyring.go create mode 100644 internal/security/patterns.go create mode 100644 internal/security/prefilter_test.go create mode 100644 internal/security/rotation_test.go create mode 100644 internal/security/ssh_fp_test.go create mode 100644 internal/security/ssh_surface_test.go create mode 100644 internal/security/ssh_test.go create mode 100644 internal/security/store.go create mode 100644 internal/security/store_test.go create mode 100644 internal/security/stress_test.go create mode 100644 internal/security/survey_test.go create mode 100644 internal/systemd/migrate.go create mode 100644 internal/systemd/migrate_test.go create mode 100644 internal/systemd/sync.go create mode 100644 third_party/homeagent-sdk/sdk/facilities.go create mode 100644 third_party/homeagent-sdk/sdk/rawfiles.go create mode 100644 third_party/homeagent-sdk/sdk/secrets.go diff --git a/Makefile b/Makefile index 6f4ecf74..9ac13b2e 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,11 @@ GO=go GOCACHE=/tmp/gocache export GOPATH=/tmp/gopath BUILD_DIR=build +# GRAPH_FILE 是启动依赖图的制品名(必须与 kernelboot.GraphFileName 一致)。 +# +# ★ 图的来源是**各模块自己的 Requires()/Provides()**(不再有手工 JSON 清单): +# 生成器 import internal/kernelmod/all(各模块 init 自注册)⇒ 算分层 ⇒ 落图。 +GRAPH_FILE=depgraph.bin PROJECT_ROOT := $(CURDIR) # 版本号来源(见 docs/git-branching.md §2.1): # @@ -281,7 +286,36 @@ check-csrc-full: check-csrc csrc-fuzz build: @mkdir -p $(BUILD_DIR) - CGO_ENABLED=1 $(GO) build $(TAG_ARGS) -trimpath -installsuffix dynlink -ldflags '$(LDFLAGS)' -o $(BUILD_DIR)/$(BINARY) ./cmd/homed/ + @# ★ 编译期定序:先由生成器(import 模块登记表)算出启动顺序图, + @# 取 digest,再构建 homed 并把 digest 注入二进制。 + @# + @# 为何必须"先出图后构建"(设计 §2.5.2):启动时 homed 比对 + @# 「磁盘图的 digest」与「内嵌 digest」,不一致 ⇒ **拒绝启动**。 + @# 两边必须来自**同一次构建**。 + @# + @# 为何要这个保护:它能拦住最危险的一类故障 —— 图文件自身完好 + @# (自校验通过),但描述的是**旧代码**(改了 Requires 没重出图) + @# ⇒ 按旧图调度 ⇒ 顺序错(正是要消灭的"静默顺序错")。 + @# + @# ⚠ 本段必须写成**一条 shell 命令**(\ 续行): + @# Make 的每行 recipe 是独立 shell,普通变量跨行不保留。 + @# + @# ★★ ldflags 的引号必须让 $${GRAPH_HASH} **能展开**(2026-10-17 实测缺陷): + @# 原写法把整串包在**单引号**里 ⇒ shell 里单引号不做变量展开 + @# ⇒ 注入进二进制的是**字面量** `$$GRAPH_HASH` ⇒ 内核启动时比对 + @# 「磁盘图 digest」vs「内嵌的 $$GRAPH_HASH」必然不等 ⇒ **拒绝启动**。 + @# 而 `make build` 日志全绿(图生成了、hash 也"注入"了), + @# 只有真跑内核才暴露。修法:用**双引号**包 ldflags。 + @# + @# ⚠ 上面这条注释**不能挪到 recipe 中间**:注释行会打断 \ 续行链, + @# 使后半段成为**独立的 shell**(变量丢失 ⇒ hash 为空)。 + @GRAPH_HASH=""; \ + CGO_ENABLED=1 $(GO) build $(TAG_ARGS) -o $(BUILD_DIR)/homed-depgraph ./cmd/homed-depgraph/ && \ + GRAPH_HASH=$$($(BUILD_DIR)/homed-depgraph -digest) && \ + $(BUILD_DIR)/homed-depgraph -print && \ + $(BUILD_DIR)/homed-depgraph -out $(BUILD_DIR)/$(GRAPH_FILE) && \ + echo "已注入启动依赖图 hash: $$GRAPH_HASH" ; \ + CGO_ENABLED=1 $(GO) build $(TAG_ARGS) -trimpath -installsuffix dynlink -ldflags "$(LDFLAGS) -X github.com/JianFeeeee/HomeAgent/internal/kernelboot.EmbeddedGraphHash=$$GRAPH_HASH" -o $(BUILD_DIR)/$(BINARY) ./cmd/homed/ @echo "Built: $(BUILD_DIR)/$(BINARY) ($(VERSION), tags='$(HOMED_TAGS)')" @go version -m $(BUILD_DIR)/$(BINARY) | grep -q 'onnxruntime' \ || echo "WARN: 本次构建不含 onnxruntime,本地向量空间不可用(HOMED_TAGS= 显式关掉时才符合预期)" diff --git a/assets/skills/commondev-discipline/SKILL.md b/assets/skills/commondev-discipline/SKILL.md new file mode 100644 index 00000000..9d665310 --- /dev/null +++ b/assets/skills/commondev-discipline/SKILL.md @@ -0,0 +1,871 @@ +--- +name: commondev-discipline +description: commondev 主管式多仓库开发流水线的使用与开发纪律。当你要为某个工程开一条分支、把工程派给一个下属 agent、或用 pi 拉起执行 agent 干活时必读。核心动作:为每个工程对话新建驻留式子 agent 并把该工程的一对通道划给它(否则所有工程都挤在根 agent 上)。**git 托管平台的操作(Gitea/GitHub/GitLab)不在插件里,而在本文** —— 用 enc_shell + 保存的凭据调 curl/CLI 完成。也含 hmap 插件开发与部署纪律。 +version: 2.0.0 +author: HomeAgent +--- + +# commondev 纪律(主管模式 · 工程对话 · 平台操作) + +本技能三部分,**都可能与你的任务直接相关**: + +- **第一部分:使用者纪律** —— 怎么开工程、怎么派活。 +- **第二部分:★ git 平台操作** —— **本文是权威**。开 PR、审批、分支保护怎么做。 +- **第三部分:开发与部署纪律** —— 怎么不踩坑。 + +> ## ★ 架构边界(用户拍板 2026-10-15) +> +> | 层 | 负责 | +> |---|---| +> | **插件 commondev** | 项目台账、工作目录、pi 会话与通道、判据与变异复核、冲突预检、凭据保存与安全执行 | +> | **本文(skill)** | **各 git 托管平台的认证头、API 端点、PR 与分支保护流程** | +> +> **插件里没有平台逻辑。** 操作 Gitea/GitHub/GitLab 等平台一律: +> 读本文 → 用 `enc_shell` + `@{凭据名}` 执行 `curl`/`gh`/`tea`。 +> +> ⇒ **新增一个托管平台只需改本文,不需要改插件、不需要重编译、不需要重启服务。** +> +> 原本这些在插件里(`platformSpec` / `commondev_account_*` / `commondev_pr_*` / +> `commondev_protect_branch` / `commondev_repo_search`),代价是: +> 每接一个平台都要改 Go 代码 → 重编译 → 重打包 → 重启, +> 且**未列出的平台直接报错**(GitLab/Gitee/Bitbucket 全不支持)—— +> 而 agent 用 curl 本来什么平台都能打。这 10 个工具已移除。 + +> 看不懂某个词时的权威文档(按顺序读): +> 1. `docs/zh/commondev-design.md` —— 目标机制、台账 schema、判据与变异、安全(§12 是架构变更)。 +> 2. `docs/zh/commondev-agent-runtime-design.md` —— 运行时与内核接入(续篇)。 +> 3. `docs/zh/kernel-secret-firewall-design.md` —— 凭据模块(enc_* 的底座)。 +> 4. `docs/zh/resident-subagent-design.md` —— 驻留子设计的原始文档。 +> +> **本文与文档冲突时,以文档为准,并回来修正本文。** + +--- + +## Setup: 第二部分 git 平台操作(本文是权威,不要凭记忆写 API) + +### 11. 所有平台都是同一个形状 + +「带认证头发一个 REST 请求」。用 `enc_shell` + `curl` 即可: + +``` +enc_shell(command="curl -s -H \"<认证头>\" <路径>") +``` + +**差异只有两个维度:API 前缀 与 认证头。** 其余是 REST 常规。 + +### 12. 认证头与 API 前缀(逐平台,★ 四种形状互不相同) + +| 平台 | API 前缀 | 认证头 | +|---|---|---| +| **Gitea** | `/api/v1` | `Authorization: token ` ← **不是 Bearer** | +| **GitHub** | `https://api.github.com` | `Authorization: Bearer ` | +| **GitLab** | `/api/v4` | `PRIVATE-TOKEN: ` ← **不是 Authorization** | +| **GitCode** | `https://api.gitcode.com/api/v5` | `Authorization: Bearer ` | +| **Gitee** | `https://gitee.com/api/v5` | `access_token=`(**查询参数**,不是头) | + +⚠ 头名、前缀、甚至「用头还是用查询参数」都不同。**未知平台先查官方文档,不要猜形状。** + +### 13. 先保存凭据,再用它 + +``` +enc_store(alias="gitea-token", value="${GITEA_TOKEN}") # 值优先走 ${ENV} 或 @file: +enc_shell(command="curl -s -H \"Authorization: token @{gitea-token}\" http://192.168.2.106:3000/api/v1/user") +``` + +⚠ **优先用 `enc_store`,不要用 `commondev_account_add`** —— 后者已不存在 +(它内建了平台语义:platform/role/base_url,那正是被移出的东西)。 + +**先验凭据再干活**(早失败,别推了一半才发现 token 错): + +``` +enc_shell(command="curl -s -o /dev/null -w '%{http_code}' -H \"Authorization: token @{gitea-token}\" http://192.168.2.106:3000/api/v1/user") +# 期望 200;401/403 = 凭据无效或权限不足 +``` + +### 14. 常见操作(以 Gitea 为例,其他平台换前缀与头) + +```bash +# 搜仓库 +curl -s -H "Authorization: token @{t}" "http://host/api/v1/repos/search?q=myname&limit=20" + +# 看仓库 / 分支 +curl -s -H "Authorization: token @{t}" "http://host/api/v1/repos/jianf/MyProj" +curl -s -H "Authorization: token @{t}" "http://host/api/v1/repos/jianf/MyProj/branches" + +# 开 PR(head=你的分支,base=目标分支) +curl -s -X POST -H "Authorization: token @{t}" -H 'Content-Type: application/json' -d '{"title":"...","head":"feat/x","base":"main"}' "http://host/api/v1/repos/jianf/MyProj/pulls" + +# 查 PR 状态 +curl -s -H "Authorization: token @{t}" "http://host/api/v1/repos/jianf/MyProj/pulls/7" + +# 合 PR +curl -s -X POST -H "Authorization: token @{t}" -H 'Content-Type: application/json' -d '{"Do":"merge"}' "http://host/api/v1/repos/jianf/MyProj/pulls/7/merge" +``` + +**push 时凭据不进 remote URL**: + +```bash +# ✅ 一次性注入(仅本次命令);clone 出来的 remote 保持干净 +enc_shell(command="cd && git -c http.extraheader=\"Authorization: token @{t}\" push origin feat/x") +``` + +⚠ 若改用 `https://token@host/...` 形式,token 会**持久化进 `.git/config`** —— +删掉临时目录也删不掉凭据(`git remote -v` 就能读出来)。设计 §7.2 明令禁止。 + +### 15. Gitea 分支保护:让「必须复审」成为**机制**而非礼貌 + +**光有流程约定不算机制** —— 执行 agent 可以直接推主干。必须让**平台**拦。 + +```bash +curl -s -X POST -H "Authorization: token @{t}" -H 'Content-Type: application/json' -d '{"enable_push":false,"required_approvals":1,"block_admin_merge_override":true,"dismiss_stale_approvals":true}' "http://host/api/v1/repos/jianf/MyProj/branch_protections?name=main" +``` + +四个开关的意义(缺一个机制就有洞): + +| 开关 | 作用 | 缺了会怎样 | +|---|---|---| +| `enable_push: false` | 禁止直接推送 | 执行 agent 直推主干,绕过评审 | +| `required_approvals: ≥1` | 至少一个审批 | 自己就能合 | +| `block_admin_merge_override: true` | admin 也不能绕过 | 用 admin token 的人绕过一切 | +| `dismiss_stale_approvals: true` | 新提交作废旧审批 | 先批准再改代码,批准白拿 | + +**真机实测(Gitea 1.24.2)**: + +| 动作 | 普通用户 token | admin token | +|---|---|---| +| push `main` | ❌ `pre-receive hook declined` | ⚠ 可能成功(见下)| +| push `feat/*` | ✅ | ✅ | +| 自合 PR | ❌ `Does not have enough approvals` | ⚠ 取决于开关 | +| 审批 → 合 PR | — | ✅ | + +⇒ **推论:执行侧必须用普通用户 token。** 用 admin token 会**静默绕过分支保护** +(看起来正常,实际机制失效且无报错)—— 这是最容易搞错的一点。 + +⚠ 分支保护已存在时用 **PATCH**(POST 会 403 `already exist`)。 + +### 16. 审批:三个必踩的 API 坑(实测) + +1. **`event` 必须是 `APPROVED`,不是 `APPROVE`。** + 传 `APPROVE` 时请求**返回 200 且不报错**,但 review 状态落成 `PENDING` —— + 于是 `required_approvals` 永远不满足,合并时报 `Does not have enough approvals`。 + 表象(权限不足)与真因(枚举值少个 `D`)毫无关联,极难自查。 +2. **必须带 `commit_id`** —— 不带时同样落成 `PENDING`(与 `event` 是两个独立条件)。 +3. **`required_approvals` 也约束 admin** —— 别指望用 admin 跳过审批。 + +```bash +# ① 先取 head sha(审批必须绑定**具体提交**,不是"移动的目标") +enc_shell(command="curl -s -H \"Authorization: token @{t}\" http://host/api/v1/repos/jianf/MyProj/pulls/7 | grep -o '\"sha\":\"[a-f0-9]*\"' | head -1") +# ② 审批(event 必须 APPROVED,且带 commit_id) +enc_shell(command="curl -s -X POST -H \"Authorization: token @{t}\" -H 'Content-Type: application/json' -d '{\"body\":\"通过\",\"event\":\"APPROVED\",\"commit_id\":\"\"}' http://host/api/v1/repos/jianf/MyProj/pulls/7/reviews") +``` + +**失败时保留平台原话**:`Does not have enough approvals` 是区分「机制生效」 +与「配置错了」的唯一线索,别把它翻译掉或吞掉。 + +--- + +## Usage: 第一部分 使用者纪律 + +> **★ 设计总纲 [已定]**:插件对 pi 做的事,本质就是 +> **「把给 pi 发消息包装一下、把 pi 返回包装一下」**,深度融合内核的 IO 抽象与中断系统 +> —— 即 **pi = 一个 Device**。**不要自己造调度器 / 会话路由 / 中断栈**, +> 全部接到内核已有的那套上(`InjectInputTo` / `RegisterOutputChannel` / `InjectInterrupt*Opts`)。 + +### 21. 角色与单位 + +| 角色 | 是谁 | 干什么 | +|---|---|---| +| **人** | 老板 | 只与主管对接 | +| **主管** | 你(长期驻留的 HomeAgent 主 agent) | 拆任务、**定义判据**、复审、决定合入 | +| **执行 agent** | 按工程拉起的一个 pi / dsh 实例 | 在某棵工作树里干活 | +| **commondev 插件** | 外部独立插件 | 仓库/凭据/分支/PR/测试,全部机械化操作 | + +**为何能用内核机制**(而不是自己造): + +| 内核概念 | pi 侧对应 | 落点(内核已有) | +|---|---|---| +| **给 pi 发消息** | `pi.prompt` / `steer` / `follow_up` | `InjectInputTo(source, "commondev/", …)` | +| **pi 返回** | sidecar 回调 / `tool_execution_*` / `agent_settled` | `InjectTextTo(source, "commondev/", …)`(按 inputch Owner 路由) | +| **中断 pi** | `steer`(插到当前轮之后) | `InjectInterruptTextOpts(..., Priority: L3)` | +| **音声/图像产物** | pi 可产出的媒体 | `OutputCapabilities()` 声明 | + +⇒ **插件只做两件额外事**:① 把 pi 接成 Device;② 台账记 `session_id` 并断言未变。 +调度/中断/路由/记忆策略**全是内核已经在做的事**。 + +### 22. 「工程对话」是一等对象 + +它是四元组,缺一不可: + +``` +工程对话 (id = br--) +├─ workdir <插件数据目录>/program/- +│ ★ hash = H(项目+分支) 是**唯一性来源** +│ ⇒ **同一仓库的不同分支必然是不同目录**(否则两 agent 共用工作树、改动互相污染) +│ ★ name = 分支名派生,**仅做可读性**,不承担唯一性 +│ (派生后可能撞:`feat/a` 与 `feat.a` 都成 `feat_a`,所以必须带 hash) +│ 独立 clone(非 worktree、非 --reference);合入后**整目录删除** +├─ session_id pi 会话(★ 续投的唯一依据) +├─ runtime local(默认,内嵌) | ssh(远端同样建目录拉 pi) +├─ host runtime=ssh 时的目标主机(**branch_open 时给**,见 §24.1) +├─ agent_cli 执行后端(默认 pi;其他 CLI 需自备 sidecar,见 §24.2) +└─ channels 插件为该工程注册的一对通道 + ├─ inputch commondev/ + └─ outputch commondev_ +``` + +**一个仓库可并行开发**:每分支一份独立 clone,互不污染、可同时推进。 +但并行有前提——必须是**互不相干的模块**;有依赖的任务请先拆边界。 + +> **为何不能用 worktree / `--reference`**(实测):worktree 删后有 +> `.git/worktrees/` 残留(需 `prune`);`--reference` 有 alternates 外部依赖, +> mirror 被清即损坏。**本地 clone 对象为硬链接**(实测 inode 相同)⇒ 省磁盘、 +> 无外部依赖、刪掉即彻底。代价:失去 worktree「同分支不可双挂」的保护 +> ⇒ 台账层必须补**唯一性闸**(同名未结分支响亮拒绝)。 + +### 23. ★ 必做:为每个工程对话新建驻留子,并把通道划给它 + +**★ 顺序很关键**:通道是**拉起 pi 拿到 session 之后**才注册的,所以你的动作是: + +``` +建工程 ──► commondev_agent_up(拉起 pi,拿到 session_id) + └─► 插件**此刻才**注册通道 inputch commondev/ + outputch commondev_ + └─► ★ 你建驻留子并把这对通道划给它 +``` + +**为何必须先有 session**:通道是“与某个具体 pi 会话对话的入口”,没 session 就是空壳。 +顺序错了(先建子)会拿到一个指向不存在通道的 `output_send__…`。 + +**插件只能把通道登记出来;「划入(assign)」只有内核能做。** 所以这一步**必须由你(agent) +自己调内核工具完成**,插件无法代劳。 + +``` +resident_agents create + id = dev- + input_chs = commondev/ ← 划入该 inputch(可用斜杠) + allowed_outputs = commondev_ ← 授权该 outputch(★ 下划线,见下) + capacity = 64 + task_prompt = "你在 推进 。 + 下任务给自己用 output_send__commondev_(payload=…), + pi 的结果会回投到你的 commondev/; + 有结论用 notify_parent 汇报。" +``` + +**★ 下任务不需要新工具**:给 pi 发消息就是子调**内核原生的输出门** +`output_send__commondev_(payload="…")` ⇒ 打到插件的通道 handler ⇒ 插件 +查台账拿到 `session_id` 后 `sidecar.prompt()`。**插件不提供 `agent_send` 之类的工具**。 + +**⚠️ 通道命名(很容易踩)**: + +| | 能用 `/` 吗 | 为什么 | +|---|---|---| +| **inputch** | ✅ | 内核自己就用 `child/` | +| **outputch** | ❌ **绝对不行** | 它会拼成 `output_send__`,上游要求函数名 `^[a-zA-Z0-9_-]{1,64}$`;违反的后果不是“这个工具不可用”,而是**整条请求 400、agent 整体失语** | + +⇒ 所以 inputch 叫 `commondev/`、outputch 叫 `commondev_`。 +`` 本身也只能用 `[A-Za-z0-9_-]`。 + +**为什么必须这么做(不是可选的优化):** + +1. inputch 是**最基本的输入路由单位**,路由按 `Owner` 在**进内核之前**完成。 + 划给子之后,**该工程的输入只流向那个子**,根 agent 看不到它 —— 这正是 + 多工程并行能做到"互不串台"的原因。 +2. **不划会的代价**(如果你偷懒):所有工程对话的输入都涌向**根 agent**, + 根成了瓶颈、上下文被 N 条工程的细节淹没,多工程并行**等于没做**。 + 现场教训(`resident-subagent-design.md`):子挂着 `inputch=[timer]` 而 timer + 的输入打在父身上,**子的轮次永远是 0** —— 登记了不等于划了。 + +**落地判据**(怎么知道做对了): + +- 投一条输入到 `commondev/`,应只被 `dev-` 处理; +- 根 agent 的轮次**不**因这条输入增加; +- `input_channels` 工具(内核自带,单工具多视图)用 `view=by_agent` 能看到划分总览。 + +### 24. 形态选择、自包含运行时与远端环境 + +**默认本地内嵌**(进程内 pi 实例);通过配置 `runtime=ssh` 切到**远程**, +远端**同样创建目录并在该目录拉起 pi**。 + +**★ 自包含运行时**:插件**自带独立的 node 与独立的 pi**,都装在**插件工作目录**, +**pi 的插件也装到这个目录** —— 不靠宿主机的 node/pi,也不污染用户的 `~/.pi`。 +**独立 node/pi 随包分发**(不首次下载、不依赖用户装)。 + +``` +<插件数据目录>/runtime/ + node/ pi/ + agentdir/ ← 每个内层 pi 必须传 PI_CODING_AGENT_DIR 指这里 + settings.json ← pi 自己的配置(偏好 / 默认 model / 全局包声明) + models.json ← pi 自己的配置(★ LLM 源写这里) + auth.json ← pi 自己的配置(key / OAuth,0600) + mcp.json ← pi 自己的配置(MCP 服务器) + skills/ prompts/ themes/ extensions/ +``` + +#### 24.1 远端(ssh)怎么开 + +两个轴**正交**,开线时一起给: + +``` +commondev_branch_open(project="p", branch="feat/x", + runtime="ssh", ssh_host="build@10.0.0.9") +``` + +- `runtime=ssh` 而**没给 `ssh_host`** → 直接拒绝(不会拖到拉起时才报) +- **未知 runtime**(比如 `k8s`)→ 响亮拒绝,**不静默降级为 local** + (静默降级会让"我以为跑在远端"一路错下去) +- 远端前置条件(**先配好,不要指望交互输密码**): + - 从本机到目标机的**免密**(`BatchMode=yes`,卡在密码提示会超时) + - 远端有 node(不在 PATH 里就配 `ssh_node_path`,例如 `/opt/node/bin/node`) + - **必配两个根目录**(缺任一,远端线开不起来): + - `ssh_workdir_root` — 远端放**工程工作目录**的根 + - `ssh_runtime_root` — 远端放 **sidecar 脚本**的目录 +- **★ 工作目录在远端准备**:本地 `clone --mirror` 出来的目录远端看不到, + 所以远端线走的是**远端建目录 → 远端共享 mirror(`/.mirrors/`)→ + 远端 clone → 远端切分支**。同一远端上的多条分支线共享一份 mirror。 + 台账里的 `workdir` 会被改写成**远端路径**(不是本地路径)。 + 远端拉取要求**远端自己能认证该仓库** —— 本插件**不把凭据推到远端** + (凭据不进工作目录、不进 remote URL)。拉不动就响亮报错。 +- 每次拉起会 `scp` 把 sidecar 推过去(幂等)——**不推就会跑上一次的旧脚本**, + 症状是"改了没生效",极难归因。推送失败**响亮报错**,不静默用旧脚本。 +- 用 `commondev_agent_state` 看 `ssh_host` 确认打到哪台机。 + +#### 24.2 接其他 CLI(zcode 等)——**不改插件核心** + +执行后端是**声明式**的:放一个目录就多一个后端。 + +``` +<插件数据目录>/runtime// + driver.json ← 描述:脚本名 / 参数 / 环境变量 / LLM 源策略 + sidecar.mjs ← 实现 sidecar 契约(ping / prompt / state / ...) +``` + +**脚本查找链**(按优先级,go 侧 `locateSidecarScript`): + +| 顺序 | 位置 | 用途 | +|---|---|---| +| ① | `<插件数据>/runtime//