mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-31 14:27:30 +00:00
## 是什么
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 条「判据本身的判据」(防止判据假绿)。
72 lines
2.4 KiB
Go
72 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// ★ 核心判据(用户 2026-10-14 明确要求):
|
|
// **同一个仓库的不同分支,必然是不同的目录** —— 否则两个 agent 会共用工作树,
|
|
// 未提交改动互相污染,"独立 clone" 的前提就不成立了。
|
|
func TestSameRepoDifferentBranchesGetDistinctDirs(t *testing.T) {
|
|
root := "/data/program"
|
|
cases := []struct{ project, branch string }{
|
|
{"TrueAgent", "feat/a"},
|
|
{"TrueAgent", "feat/b"}, // ← 同仓库、不同分支:必须不同
|
|
{"TrueAgent", "main"},
|
|
{"TrueAgent", "feat/a"}, // ← 与第一条同:必须相同(可重算)
|
|
{"Other", "feat/a"}, // ← 不同仓库、同名分支:必须不同
|
|
{"TrueAgent", "feat/a/b"}, // 嵌套斜杠
|
|
{"TrueAgent", "feat.a"}, // 点(与 feat/a 不同名,应不同目录)
|
|
}
|
|
seen := map[string]string{}
|
|
for _, c := range cases {
|
|
got := branchDir(root, c.project, c.branch)
|
|
key := c.project + "|" + c.branch
|
|
if prev, dup := seen[got]; dup && prev != key {
|
|
t.Errorf("目录冲突:%s 与 %s 都得到 %s", prev, key, got)
|
|
}
|
|
seen[got] = key
|
|
}
|
|
|
|
// 明确钉住用户关心的那条
|
|
a := branchDir(root, "TrueAgent", "feat/a")
|
|
b := branchDir(root, "TrueAgent", "feat/b")
|
|
if a == b {
|
|
t.Fatalf("同仓库不同分支得到同一目录:%s", a)
|
|
}
|
|
// 可重算:同输入必同输出(清理孤儿目录依赖这一点)
|
|
if branchDir(root, "TrueAgent", "feat/a") != a {
|
|
t.Fatal("目录名不可重算")
|
|
}
|
|
}
|
|
|
|
// 目录名形态:<hash>-<name>,且 name 可读(含分支信息)。
|
|
func TestDirNameShape(t *testing.T) {
|
|
d := dirName("TrueAgent", "feat/cool-thing")
|
|
hash, name, ok := parseDirName(d)
|
|
if !ok {
|
|
t.Fatalf("目录名 %q 不可解析", d)
|
|
}
|
|
if len(hash) != identityHashLen {
|
|
t.Errorf("hash 位数不对: %q", hash)
|
|
}
|
|
if name != "feat_cool-thing" {
|
|
t.Errorf("name 应可读(分支派生): %q", name)
|
|
}
|
|
// 目录内的路径应落在 program/ 下
|
|
if filepath.Dir(branchDir("/x/program", "P", "b")) != "/x/program" {
|
|
t.Error("目录未落在 program/ 下")
|
|
}
|
|
}
|
|
|
|
// 非法字符被派生掉(目录名必须能安全用于文件系统与通道名)。
|
|
func TestDirNameSanitized(t *testing.T) {
|
|
for _, br := range []string{"feat/a b", "中文", "a.b", "a/b"} {
|
|
d := dirName("P", br)
|
|
if !validName(d) {
|
|
t.Errorf("分支 %q 派生的目录名 %q 含非法字符", br, d)
|
|
}
|
|
}
|
|
}
|