mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-31 06:17:25 +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 条「判据本身的判据」(防止判据假绿)。
73 lines
2.1 KiB
Go
73 lines
2.1 KiB
Go
package main
|
||
|
||
// 通用小工具:时间、数值参数、HTTP 客户端。
|
||
//
|
||
// 这些原本散在 platform.go 里(那个文件是 git 平台专有的,已随平台逻辑一并
|
||
// 移出插件)。但它们本身与 git 无关 —— criteria / pi_tools / encstore 都在用,
|
||
// 所以单独留在这里。**新增平台专属代码不要再往本文件加。**
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
func nowRFC3339() string { return time.Now().Format(time.RFC3339) }
|
||
|
||
// numArg 取数值参数(JSON 数字进来是 float64)。
|
||
func numArg(args map[string]interface{}, key string) float64 {
|
||
switch v := args[key].(type) {
|
||
case float64:
|
||
return v
|
||
case int:
|
||
return float64(v)
|
||
case int64:
|
||
return float64(v)
|
||
case json.Number:
|
||
f, _ := v.Float64()
|
||
return f
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// httpClient 是共享客户端(超时必须有:对端不可达时不能挂住工具调用)。
|
||
var httpClient = &http.Client{Timeout: 30 * time.Second}
|
||
|
||
// httpGetJSON 发一个 GET 请求,返回 body 与状态码。
|
||
//
|
||
// 不带认证头:本插件已不持有任何平台凭据。需要认证的调用应当
|
||
// 用 **safeshell** 执行 curl(或 CLI),凭据由内核变量库按 @{名字} 填充 ——
|
||
// 那是「凭据只保存不读取」这条边界要求的方式。
|
||
func httpGetJSON(url string, headers map[string]string) (string, int, error) {
|
||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||
if err != nil {
|
||
return "", 0, err
|
||
}
|
||
for k, v := range headers {
|
||
req.Header.Set(k, v)
|
||
}
|
||
req.Header.Set("Accept", "application/json")
|
||
req.Header.Set("User-Agent", "commondev/0.1")
|
||
|
||
resp, err := httpClient.Do(req)
|
||
if err != nil {
|
||
return "", 0, err
|
||
}
|
||
defer resp.Body.Close()
|
||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||
return string(b), resp.StatusCode, nil
|
||
}
|
||
|
||
// stripTokenFromURL 把 URL 里的凭据抹掉,用于日志/回显。
|
||
func stripTokenFromURL(u string) string {
|
||
if i := strings.Index(u, "://"); i >= 0 {
|
||
rest := u[i+3:]
|
||
if at := strings.Index(rest, "@"); at >= 0 {
|
||
return u[:i+3] + "***@" + rest[at+1:]
|
||
}
|
||
}
|
||
return u
|
||
}
|