mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
只改仓库内文件的纯展示品牌名与已随批 2 落地的安装路径;运行时契约、仓库 URL、
域名、环境变量、可执行名、插件名一律不动。
一、纯展示名(HomeAgent → HologramAgent,共 44 处 / 18 个文件)
- README.md 4、README_EN.md 4(标题、client 变体说明、Windows 安装包名、AGPL 段)
- docs/ 8 个文件 14 处(git-branching 1、ci-cd-runbook 1、commondev-design 3、
commondev-agent-runtime-design 4、deploy-runbook 1、legacy-issues 1、
prod-migration-checklist 1、triple-extract-attention 2)
- assets/docs/ 6 个文件 14 处(zh/en 的 OVERVIEW 2+2、ARCHITECTURE 2+2、
PLUGIN_DEV 3+3)
- assets/branding/mascot-xiaozhai-guide.md 2 处(身份行、标志名)
- site/index.html 6 处(title、meta description、og:title、logo alt、页头字、
插件卡片文案 "HologramAgent 接入 AgentMail")
二、安装路径对齐批 2 产物(共 10 处)
- /usr/lib/homeagent → /usr/lib/hologram(7 处:README 2、README_EN 2、
multimodal-space 3)
- /usr/share/doc/homeagent → /usr/share/doc/hologramagent(3 处:README 1、
README_EN 1、multimodal-space 1;注意 Doc 路径带 agent 后缀)
- site/index.html 的 19 处 HomeAgent 里,13 处是 github.com/JianFeeeee/HomeAgent
仓库 URL,属红线整串保留;只改 URL 之外的展示文案(其余 13 处零改动)
三、明确不改(红线 / 一致性冲突)
- site/index.html 的 23 个 github.com/JianFeeeee/homeagentsdk 外链(21 条
example + 2 条纯仓库),SDK 仓改名属批 4-b,一个未动
- 核心仓仓库 URL、sdk./introduce.homeagent.jianfgit.xyz 域名、HOMEAGENT_ 前缀、
homed/waiter/hmapdev、.hmap、homeagent-mail-bridge 等插件名、third_party/、
internal/ 与 cmd/ 全部零改动
- ~/.homeagent 路径本批保持原样:cmd/waiter/main.go:109、daemon.go:290 硬编码
仍为 .homeagent,全仓无 .hologram 命中;照规格改文档会指向程序不用的目录,
需与代码侧那条线一起改(遗留项,已单独上报)
判据:文档区展示名残留归零(44 文件)、红线计数与 base e3584bb 逐项相同、
三条变异均被咬住且还原后复绿。
1104 lines
55 KiB
Markdown
1104 lines
55 KiB
Markdown
# CommonDev:主管式多仓库开发流水线(设计 · 未实现)
|
||
|
||
> **状态:架构已变更(见 §12)。** **git 托管平台专有的工具**已移出插件、进 skill;
|
||
> 其余机制(台账、工作目录、pi 会话与通道、判据与变异复核、冲突预检、凭据)保留。
|
||
> §4.1 里的平台工具清单**不再是实现目标**;§13 保留历史实现审计供追溯。
|
||
> 接手者请从 §12 开始读。
|
||
>
|
||
> **续篇(必读)**:`docs/zh/commondev-agent-runtime-design.md` —— 运行时与内核接入
|
||
> (工程对话四元组、内嵌/SSH 两形态、inputch 路由、L4 归属、语义缺口)。
|
||
> **本文 §6.1 的结论已被续篇修正**(只论证了 cwd 一维)。
|
||
>
|
||
> **★ 安全模块(计划追加)**:`docs/zh/kernel-secret-firewall-design.md` ——
|
||
> 敏感信息拦截**下沉为内核安全模块**(用户纠正:LLM 调用本身不可信,插件拦截不算边界)。
|
||
> 该文成立后,**本插件侧的加密 shell 会大幅变薄**(不再自带加密库/别名表)。
|
||
>
|
||
> **★ 设计总纲 [已定|2026-10-13]**:插件对 pi 做的事,本质就是
|
||
> **「把给 pi 发消息包装一下、把 pi 返回包装一下」,并深度融合内核的 IO 抽象与中断系统**
|
||
> —— 即 **pi = 一个 Device**。**不自己造调度器/会话路由/中断**,全部接到内核已有的那套上。
|
||
> 详见续篇 §2.5 / §3.1。
|
||
>
|
||
> **标记约定**(沿用本仓惯例):
|
||
> - **[已核]** = 本文写作时已在本机实测/源码核对确认,附证据
|
||
> - **[待定]** = 需要决策,本文给出建议但未拍板
|
||
> - **[风险]** = 已知会让整套机制失效的点
|
||
>
|
||
> 撰写时间:2026-10-13。事实基线:TrueAgent 工作区 `/home/program/TrueAgent`,
|
||
> 生产实例 `/home/newqqagent`(homed PID 2966649 guard / 2966664 agent)。
|
||
|
||
---
|
||
|
||
## 1. 背景:这套机制要解决什么
|
||
|
||
### 1.1 用户的原话(原样引用,避免转述走样)
|
||
|
||
> 「其实我想的是以后你就是开发主管,我以后只跟你对接,然后你有一个 commondev 插件,
|
||
> 包含在远程 git 仓库全套操作支持添加多个账号搜索账号仓库等一系列能力)然后有项目列表
|
||
> 每个项目有独立的一系列(分支,对应分支分配给的 agent,分支进行的工作与验收与否)
|
||
> 然后具体实现是通过临时目录(插件数据目录下)有 program/hash-name 目录,每个目录下是
|
||
> 独立仓库的独立分支,每次修改包含(更新,bugfix 等)都是拉取仓库,拉出分支,修改,
|
||
> agent 自审,你复审,你调用插件提 pr 合入(注意审核这部分是插件提供一组让你定义单元
|
||
> 测试的工具,你定义过单元测试后,对应 agent 完成修改后你直接让插件进行复核即可)
|
||
> 插件中包含压力测试,接口测试,编译测试等的多个测试模板。」
|
||
|
||
关于多账号,用户后续澄清:
|
||
|
||
> 「我说的多账号是指插件持有多个 git 平台的访问凭据」
|
||
|
||
关于 agent 如何进目录,用户后续澄清:
|
||
|
||
> 「是 homeagent 负责把仓库拉下来,开出分支,然后在目录中拉起 agent 注入提示词,
|
||
> 这好像需要进一步完善优化 a2a/acp 或者直接调 cli」
|
||
|
||
### 1.2 角色模型 [已定]
|
||
|
||
| 角色 | 是谁 | 职责 |
|
||
|---|---|---|
|
||
| **人(用户)** | 老板 | 只与「开发主管」对接,不直接指挥执行 agent |
|
||
| **开发主管** | 长期驻留的 HologramAgent 主 agent | 拆任务、**定义判据**、复审、决定合入 |
|
||
| **执行 agent** | 按任务拉起的编码 agent(pi / dsh) | 在某棵工作树里干活 |
|
||
| **commondev 插件** | 新增 Go 插件 | 仓库/凭据/分支/PR/测试执行,全部机械化操作 |
|
||
|
||
### 1.3 为什么这件事值得做
|
||
|
||
现状是「在项目文件夹里开 agent」,导致同项目的多个 agent **无法各占分支并行**。
|
||
用户要的是:一个项目可同时有多条分支在被不同 agent 推进,而合入点由主管把控。
|
||
|
||
---
|
||
|
||
## 2. 目标机制(用户描述 + 本文补全)
|
||
|
||
```
|
||
项目列表(每项目一条)
|
||
└─ 分支列表(每分支:名字 / 分配给哪个 agent / 工作内容 / 验收状态 / base_sha)
|
||
└─ 临时工作目录(插件数据目录下 program/<hash>-<name>/)
|
||
└─ 独立仓库的独立分支(本地 clone)
|
||
└─ ★ 拉起 pi 并拿到 session_id
|
||
└─ ★ 才有通道:插件注册 inputch commondev/<id>
|
||
+ outputch commondev_<id>
|
||
└─ 主 agent 建驻留子并划入这对通道
|
||
└─ 子调 output_send__commondev_<id> 下任务
|
||
└─ agent 自审 → 主管复审 → 插件复核 → 提 PR → 合入
|
||
```
|
||
|
||
> **顺序要点(容易搞反)**:**先有 session,才有通道**。通道是“与某个具体 pi 会话
|
||
> 对话的入口”,没 session 注册出来就是空壳。所以:
|
||
> **建工程 → 拉 pi(拿 session)→ 注册通道 → 建驻留子并划入 → 投递**。
|
||
> 详见续篇 §2.2。
|
||
|
||
**插件应提供的能力面**(用户列举):
|
||
|
||
1. 远程 git 仓库全套操作(clone / fetch / branch / commit / push / PR / merge)
|
||
2. 多平台凭据(GitHub / GitCode / Gitea …)[已按用户澄清修正:多凭据,非多 agent 账号]
|
||
3. 账号/仓库搜索
|
||
4. 项目列表与分支状态台账
|
||
5. **判据定义与复核**(主管定义单元测试 → 插件在 agent 改完后执行)
|
||
6. 测试模板:压力 / 接口 / 编译
|
||
|
||
---
|
||
|
||
## 3. 已核实的事实(接手者可直接依赖)
|
||
|
||
### 3.1 仓库与平台
|
||
|
||
| 事实 | 证据 |
|
||
|---|---|
|
||
| TrueAgent 自身 remote 为 `gitcode` + `origin`(github) | `git remote -v` |
|
||
| 本机有 **Gitea 1.24.2** 在 `192.168.2.106:3000` | `curl /api/v1/version` → `{"version":"1.24.2"}` |
|
||
| 本机无 git 库依赖(go.mod 无 go-git/git2go) | 插件现只能用 `os/exec` 调 git |
|
||
| `~/.git-credentials` 存在 | `ls ~/.git-credentials` |
|
||
|
||
### 3.2 凭据现状 [风险]
|
||
|
||
| 事实 | 证据 |
|
||
|---|---|
|
||
| 插件配置存于 `config_<plugin>` 表,key→TEXT,**可存 JSON** | `config_qq.group_tool_allowlists` 即 JSON |
|
||
| 凭据**明文**存储 | `config_vikunja.token = tk_2b7ecac1...` |
|
||
| `config.db` 权限 **644(全局可读)** | `ls -l /home/newqqagent/config.db` |
|
||
|
||
> **[风险]** commondev 的凭据是**能 push 用户仓库**的凭据,与现有 token 不同量级。
|
||
> 按现有 644 明文惯例存,等于把仓库写权限暴露给同机任何可读文件的东西
|
||
> (本容器跑着数十个子进程插件、浏览器、MCP 服务)。详见 §7.1。
|
||
|
||
### 3.3 插件开发基建(可复用的现成东西)
|
||
|
||
| 事实 | 证据/位置 |
|
||
|---|---|
|
||
| 已有 `plugindev` 示例插件,含 `plugindev_status/init/build/sdk/projects` | `third_party/homeagent-sdk/example/plugindev/` |
|
||
| 插件数据目录 API | `sdk.Settings().DataDir()`(`internal/sdk/settings_impl.go:24`) |
|
||
| `hmapdev` 工具链已装 | `/usr/local/bin/hmapdev` |
|
||
| SDK 无 HTTP/exec 能力面,但插件是编译型 Go,可自行 `os/exec` | `internal/sdk/plugin.go` 方法清单 |
|
||
|
||
> commondev 可从 `plugindev` 长出来,不必从零起。
|
||
|
||
### 3.4 「把 agent 拉进目录」——三条路的真实能力 [已核,关键]
|
||
|
||
| 路径 | 能否指定 cwd | 结论 |
|
||
|---|---|---|
|
||
| `pi -p`(无头) | ✅ **cwd = 进程 cwd** | **实测**:在 `proj-b` 下运行,正确读出 `marker.txt` = `BBB` |
|
||
| `dsh headless` | ✅ 逻辑同 CLI(cwd 继承) | 实测被**凭据**挡下(非 cwd 问题) |
|
||
| `a2a_query` / `acp_query` | ❌ **参数里没有 cwd** | 见下 |
|
||
|
||
**`a2a_query` / `acp_query` 的确切形状**(已读源码):
|
||
|
||
- `a2a_query` 参数:`agent_url` / `query` / `session_id` / `timeout` —— **无 cwd**
|
||
- `acp_query` 参数:`server_url` / `prompt` / `session_id` / `timeout` —— **无 cwd**
|
||
- pi-acp 扩展的 `session/new` payload:`{"request": {"text": text}}` —— **无 cwd**
|
||
(`/root/.pi/agent/extensions/pi-acp/index.ts:127`)
|
||
|
||
**它们只能把文本发给「已在运行的」 agent**,而该 agent 的工作目录是**启动时**固定的。
|
||
|
||
> **这是用户所说「需要进一步完善优化」的准确位置。**
|
||
>
|
||
> A2A/ACP 是**长期驻留 agent** 的模型(peer-to-peer,一个 agent 一个进程一个目录);
|
||
> 而本需求是**按任务拉起**(一次性、每任务一个目录)。两者模型不匹配。
|
||
>
|
||
> - 若走 A2A/ACP:必须**扩建协议**增加 `cwd`(改动扩展 + 可能改动 ACP 实现)
|
||
> - 若走 CLI:`pi -p` **天然贴合**(进程 cwd 即工作目录),零协议改动
|
||
>
|
||
> **本文倾向 CLI 路线** [待定],理由见 §6.1。
|
||
|
||
### 3.5 端口占用:A2A/ACP 不是通用能力 [已核]
|
||
|
||
`127.0.0.1:14010`(A2A)与 `14011`(ACP)由 **PID 819 `pi-web-sessiond`** 监听,
|
||
**不是** general pi 的能力。普通 `pi -p` 启动时只打印:
|
||
|
||
```
|
||
[pi-a2a] A2A server not listening on 127.0.0.1:14010 (EADDRINUSE)
|
||
```
|
||
|
||
`pi --help` 里**没有** `--serve` 之类参数——服务由 `~/.pi/agent/extensions/pi-a2a|pi-acp`
|
||
两个扩展提供,且扩展内有「子进程不继承监听端口」的守卫。
|
||
|
||
### 3.6 拉起 agent 当前**不工作** [风险 · 最高优先级]
|
||
|
||
实测三条拉起尝试全部失败:
|
||
|
||
| 尝试 | 错误 |
|
||
|---|---|
|
||
| `pi -p --provider llmsproxy --model AUTO` | 成功(同目录测试通过),但另一轮撞 `model "claude-opus-5" is not allowed for this key`(403) |
|
||
| `dsh headless` | `AUTH: Authentication Fails, Your api key: ****d63d is invalid` |
|
||
|
||
且与 QQ 会话中 agent 自身遭遇的失败**同源**(`/home/newqqagent/memory/context.json`
|
||
第 23–28 条):
|
||
|
||
```
|
||
[23] 403: model "claude-opus-5" is not allowed for this key
|
||
[24] 503: all 6 auto providers failed
|
||
[26][27][28] 503: all 6 auto providers failed(codebuddy_kimi / vela_deepseek …)
|
||
```
|
||
|
||
> **[风险]这是整套设计的致命前提。** 主管模式、worktree 隔离、PR 流水线、变异复核——
|
||
> **每一环都要求「能成功拉起一个 agent 并让它在指定目录干活」**,而这一步当前成功率
|
||
> 无法保证。**在解决它之前,不应继续构建上层机制。**
|
||
>
|
||
> 注意:失败集中在 `llmsproxy/AUTO` 与 `justworker/claude-opus-5`。而本机大量其他
|
||
> agent 调用(子代理、go test、端到端验证)当日是成功的 ⇒ **可用 provider/model 组合
|
||
> 需要被枚举出来,而不是继续用 `AUTO` 去赌。**
|
||
|
||
### 3.7 内核侧 `spawn_child` 与本题无关 [已核]
|
||
|
||
`spawn_child` 是内核自有的**工作式轻量子**(goroutine),参数只有 `task` 与 `max_turns`
|
||
(`internal/agent/core/spawn.go:49`),**不是**拉起外部编码 agent。
|
||
用户已明确:本需求是「homeagent 拉起本机的编码 agent(pi 和 dsh)」,与此无关。
|
||
|
||
### 3.8 值得保留的历史结论(前序 git worktree 讨论)
|
||
|
||
在 QQ 会话(message_id 1685590448)中,agent 给出的 git worktree 方案,本文复核:
|
||
|
||
| agent 的说法 | 复核结果 |
|
||
|---|---|
|
||
| worktree 隔离有效、互不污染主仓 | ✅ 实测通过 |
|
||
| 「同一分支不能被两棵 worktree 同时挂,git 会直接拒绝」 | ✅ 实测 `fatal: 'feat/a' is already used by worktree at '...'` |
|
||
| worktree 共用对象库、很轻 | ✅ |
|
||
| 「给专属 agent 配 `core.agent.workdir`」 | ❌ **错**。见下 |
|
||
|
||
**`core.agent.workdir` 的两处错误理解**(`internal/config/registry.go:863`):
|
||
|
||
1. 它是 `Type: "string"` 的**全局单值**,不是 per-agent 配置 ⇒ 配了影响所有 agent
|
||
2. 它只是 `cmd_run` 的 **fallback**,agent 显式传 `workdir` 时被覆盖(`internal/plugins/cmd/plugin.go:149`)
|
||
|
||
> **教训**:worktree 方案本身没错,但「怎么把某个 agent 绑到某棵树」当时**没有机制**,
|
||
> 只有「在任务描述里写明」这种提示词层面的软约定。这正是 §3.4 的问题。
|
||
|
||
---
|
||
|
||
## 4. 设计:目标架构
|
||
|
||
### 4.1 commondev 插件的工具面(建议)
|
||
|
||
```
|
||
# 凭据与平台
|
||
commondev_account_add 添加一个平台凭据(kind: github|gitea|gitcode)
|
||
commondev_account_list 列出已配凭据(**不返回 secret 本身**)
|
||
commondev_account_test 验证某凭据可用(调平台 API 而非只看字符串非空)
|
||
commondev_repo_search 按关键字搜账号下/平台上的仓库
|
||
|
||
# 项目台账
|
||
commondev_project_add 登记一个项目(本地名 → 远程 URL + 平台 + 默认分支)
|
||
commondev_project_list 项目与分支总览(含每分支的 agent / 工作内容 / 验收状态 / base_sha)
|
||
commondev_branch_open 开一条分支工作线(分配 agent、写工作内容、记录 base_sha)
|
||
commondev_branch_status 单条分支的详细状态(含临时目录路径、最近提交、测试结果)
|
||
commondev_branch_close 收尾清理(merge 后或废弃时)
|
||
|
||
# 工作目录
|
||
commondev_workdir_prepare 准备/复用临时工作目录(mirror 派生,见 §4.3)
|
||
commondev_workdir_clean 清理临时目录(含凭据残留检查,见 §7.2)
|
||
|
||
# 内置 pi 的运行时(★ 本质是“包一下”:发消息 / 收返回,见续篇 §2.5 §3.1)
|
||
commondev_agent_up 拉起 sidecar(内嵌 pi;remote 时 ssh)并登记台账
|
||
commondev_agent_state 查 session / runtime / 健康
|
||
commondev_agent_down 收尾(幂等全清,不留孤儿)
|
||
# ★ 下任务**不走插件工具**:子调内核原生的 output_send__commondev_<id>(payload=…),
|
||
# 插件写的通道 handler 负责“包一下”→ sidecar.prompt(见续篇 §2.5)。
|
||
# “中断/纠偏 pi”走**内核已有的** InjectInterrupt(L3),也不另造工具。
|
||
commondev_pi_plugin_install ★ 为内置 pi 装插件(scope=project 临时 / plugin 全局)
|
||
commondev_pi_plugin_remove 卸载(同 scope 语义)
|
||
commondev_pi_plugin_list 列出已装(分 scope)
|
||
commondev_pi_source_import ★ 把 HologramAgent 内部配置的 LLM 源导入内置 pi
|
||
commondev_pi_source_add 添加一个源(直接写 pi 的 models.json/auth.json)
|
||
commondev_pi_source_list 列出可用源(默认**不返回** api_key)
|
||
commondev_pi_source_default ★ 指定内置 pi 默认用哪个源(写 settings.json)
|
||
commondev_pi_config_get/set 读写 pi 配置(scope=plugin|project)
|
||
|
||
# 判据与复核
|
||
commondev_test_define **主管**定义一条判据(落地成可执行脚本 + 期望)
|
||
commondev_test_run 执行判据集合(正向)
|
||
commondev_test_mutate 执行**变异验证**(反向,见 §4.5)★ 必须有
|
||
commondev_review_submit agent 完成后提交复核(正向 + 变异一起跑)
|
||
|
||
# 提 PR
|
||
commondev_pr_open 提 PR(含 conflict 预检,见 §4.4)
|
||
commondev_pr_status 查 PR 状态
|
||
commondev_pr_merge 合入(**仅主管授权的凭据可调**)
|
||
```
|
||
|
||
### 4.2 项目/分支台账(建议 schema)
|
||
|
||
```
|
||
projects
|
||
id, name, remote_url, platform, default_branch, mirror_path, created_at
|
||
|
||
branches
|
||
id, project_id, name, agent_name, work_desc,
|
||
base_sha, # ★ 用户原描述中缺失,必须补(理由见下)
|
||
head_sha, status, # status: open|review|merged|abandoned
|
||
worktree_path, created_at, closed_at
|
||
|
||
criteria # 主管定义的判据
|
||
id, project_id, branch_id, name, kind, # kind: unit|interface|stress|build
|
||
script, expect, definition_hash, created_at
|
||
|
||
review_runs
|
||
id, branch_id, criteria_id, phase, # phase: forward|mutate
|
||
result, evidence, started_at, finished_at
|
||
```
|
||
|
||
**为什么必须加 `base_sha`**(用户原描述没有):
|
||
没有它,插件无法判断「两个 PR 改了同一文件时第二个该不该 rebase、能不能自动合」,
|
||
出问题也无法重放。建议在 `commondev_pr_open` 前做 conflict 预检。
|
||
|
||
⇒ **刀 3 已落地**(见 §4.6)。
|
||
|
||
### 4.3 临时工作目录:mirror + **本地 clone(硬链接)** [已定 2026-10-13]
|
||
|
||
**用户拍板**:每个工程对话一个独立目录 `program/<hash>-<name>`(`name` = **仓库名**),
|
||
**该分支修改提交合入后直接删除该目录**。因此需要「删掉即彻底」,这直接排除了两个
|
||
候选方案(均实测)。
|
||
|
||
```
|
||
<插件数据目录>/program/
|
||
mirrors/<platform>-<repo>.git/ # git clone --mirror,每仓库一份,定期 fetch
|
||
<hash>-<name>/ # 每个工程对话一份;合入后整目录删除
|
||
```
|
||
|
||
`<hash>` 用「项目名 + 分支名」的短哈希,**保证可重算**(便于清理孤儿目录),
|
||
并且 **hash 是目录唯一性的来源**。
|
||
|
||
#### 目录映射:★ 同一仓库的不同分支必然是不同目录 [已定 2026-10-14]
|
||
|
||
用户明确要求:
|
||
|
||
> 「即使是同一个仓库,只要是不同的分支都拉到 hash-name 下使用 hash 区分,
|
||
> 插件通过哈希表映射工程分支与文件夹」
|
||
|
||
```
|
||
branchKey(project, branch) = project + "\x00" + branch
|
||
└─ shortHash() → <hash>(8 位 16 进制,可重算)
|
||
└─ program/<hash>-<name> 其中 name = 分支名派生(可读性)
|
||
```
|
||
|
||
**两条职责必须分清**(这是本节能防住的事故):
|
||
|
||
| 要素 | 承担什么 | 为什么 |
|
||
|---|---|---|
|
||
| **hash** | ★ **唯一性** | 一个仓库可并行多条分支;唯一性只能由 hash 承担,**不依赖 name** |
|
||
| **name** | 可读性 | 清理孤儿/审计时纯 hash 无信息量 |
|
||
|
||
若只按仓库名建目录,两条分支会**共用同一个工作树** —— 那是灾难性的:
|
||
agent A 的未提交改动会出现在 agent B 的 `git status` 里,且「独立 clone」的前提(§4.3)不成立。
|
||
|
||
**验证**(已实施):`branchDir("TrueAgent","feat/a") != branchDir("TrueAgent","feat/b")`,
|
||
且同输入可重算。变异(hash 只用项目名)→ 报红。
|
||
|
||
**⚠️ name 不能承担唯一性**:分支名经派生后可能撞(如 `feat/a` 与 `feat.a` 都派生为 `feat_a`),
|
||
所以目录名必须带 hash。
|
||
|
||
**映射是双向的、且只有一处真相**:目录名可由 `(project, branch)` 重算;
|
||
反查靠台账的 `branch.Workdir`(`resolveBranchDir`)。
|
||
**绝不要在映射函数之外另算目录名**。
|
||
|
||
**孤儿检测**:`program/` 下符合 `<hash>-<name>` 形态、但台账里无记录的目录 = 孤儿
|
||
(`listOrphanDirs`)。非本插件形态的目录不碰。
|
||
|
||
#### 为什么是本地 clone,而不是 worktree 或 `--reference`(均实测)
|
||
|
||
| 方案 | `rm -rf` 之后 | 外部依赖 | 结论 |
|
||
|---|---|---|---|
|
||
| **worktree** | **`.git/worktrees/<name>` 残留**,状态变 `prunable`;必须再跑 `git worktree prune` 才清 | 无 | ❌ 删除不彻底;中央仓需持续维护 |
|
||
| **`--reference`**(本稿初版建议) | 干净 | **★ `.git/objects/info/alternates` 指向 mirror** | ❌❌ **mirror 一旦被清理,work tree 彻底损坏**:实测 `error: unable to normalize alternate object path`,之后**每次** git 操作都报错 |
|
||
| **本地 clone** | **干净**(只剩源仓) | **无** | ✅ **对象是硬链接** ⇒ 省磁盘(实测两侧对象 inode 完全相同);删掉即彻底 |
|
||
|
||
```bash
|
||
git clone <mirror> <workdir> # 本地 clone:硬链接共享对象、无 alternates
|
||
```
|
||
|
||
> **为什么硬链接这件事重要**:本地 clone 复制对象时用硬链接(实测 inode 相同,如
|
||
> `5836713`),因此并行 N 份工作树**几乎不额外占磁盘**,同时**每个目录都是完整自洽的仓**
|
||
> —— 既无 worktree 的中央元数据,也无 `--reference` 的外部依赖。
|
||
> 两个要求(「删除彻底」与「不是每任务全量 clone」)同时满足。
|
||
|
||
#### 已考虑并否决的替代方案
|
||
|
||
> **用 worktree + 只删分支(不删目录)**:即「删掉分支名但留下 worktree 目录」。
|
||
> **不采纳**:残留的 checkout 会持有一个已删除分支的引用,下一次同名分支的
|
||
> worktree 创建可能冲突(实测 `fatal: 'feat/a' is already used by worktree`);
|
||
> 且目录永远删不干净,与用户要求的「合入后直接删除目录」相悖。
|
||
|
||
#### ⚠️ 失去的一条保护,必须用台账补回
|
||
|
||
worktree 天然禁止「同一分支被两棵工作树同时挂」—— 实测
|
||
`fatal: 'feat/a' is already used by worktree at '...'`。**改用独立 clone 后这条保护消失了**:
|
||
两份 clone 可以在同名分支上各自 push,后者会被拒(非 fast-forward)而暴露问题,
|
||
但**在 push 之前**两边都以为自己是唯一。
|
||
|
||
⇒ **必须在台账层补一道唯一性闸**:
|
||
|
||
- `branches` 表对 `(project_id, name, status IN ('open','review'))` 建**唯一索引**;
|
||
- `commondev_branch_open` 遇到同名且仍开着(或已进 review)的分支必须**响亮拒绝**,
|
||
不是静默复用。
|
||
|
||
(用户为「一个仓库并行开发」选择独立 clone 是**正确的** —— 代价只是这道补偿闸。)
|
||
|
||
#### 目录生命周期
|
||
|
||
```
|
||
branch_open
|
||
└─ 建 <hash>-<name>/(由 mirror 本地 clone)
|
||
└─ 干活 → 提交 → push → PR
|
||
└─ 合入(或废弃)
|
||
└─ ★ 整目录删除(不保留、不归档)
|
||
```
|
||
|
||
> **删除是收尾动作的一部分**:合入后不删,下次同名分支会与残留目录混淆;
|
||
> 并行开发的意义就是「用完即清」。与内核「子不得比父活得久、无残留」同一条纪律
|
||
> (见续篇 §4 孤儿清理)。
|
||
|
||
### 4.4 目录命名与并行开发
|
||
|
||
- 目录:`program/<hash>-<name>`,`<hash>` = 项目名+分支名短哈希(★ **唯一性来源**),
|
||
`name` = 分支名派生(**只做可读性**,不承担唯一性,见 §4.3)
|
||
- **一个仓库可并行开发**:每分支一份独立 clone ⇒ 互不污染、可同时推进
|
||
- 目录内记录 `_commondev.json`(项目/分支/agent/base_sha/凭据指纹),便于事后审计
|
||
- **不要**把凭据写进目录(见 §7.2)
|
||
|
||
> **并行是有前提的**(见 §8.4):并行的必须是**互不相干的模块**。
|
||
> 若任务间有依赖(A 改接口、B 用接口),两棵独立 clone 互不可见,
|
||
> 问题会集中到 merge 时爆发 —— 那时应先拆边界,再谈并行。
|
||
|
||
### 4.5 ★ 复核必须是两步:正向 + 变异(本文最重要的设计主张)
|
||
|
||
用户原话:
|
||
|
||
> 「我定义过单元测试后,对应 agent 完成修改后你直接让插件进行复核即可」
|
||
|
||
**这里的隐含假设是「插件 GREEN = 改对了」。该假设不成立。**
|
||
|
||
插件只能跑**主管预先写下的判据**。若判据没覆盖那条 bug,插件照样打勾。
|
||
自动化**不会**发现假绿,**只会让假绿跑得更快、更可信**。
|
||
|
||
**本仓当日的实证**:2026-10-13 下午新写的协议一致性判据组,变异测试中
|
||
**M5 未报红**——把 `json:"phase"` 改成 `json:"phaseX"`,判据仍全绿,
|
||
因为它只查了前缀。是**变异测试**把它咬出来的(该轮修复的最终判据已改为匹配完整 tag)。
|
||
|
||
**因此插件的复核必须包含两个阶段**:
|
||
|
||
| 阶段 | 做什么 | 不通过的后果 |
|
||
|---|---|---|
|
||
| **正向**(forward) | 跑主管定义的判据 → 期望通过 | 实现有 bug,驳回 |
|
||
| **反向**(mutate)★ | 自动改坏被测代码(按主管给的变异规则或自动生成)→ 期望判据**报红** | **判据太松,驳回** |
|
||
|
||
**没有反向阶段,就是在给「测试通过」这个信号不断充值信用,直到它骗一次大的。**
|
||
|
||
变异规则由主管随判据一起定义(`commondev_test_define` 可迭代幂等),存入 `criteria` 表。
|
||
注意本仓纪律:判据类写「某标识符是否出现在函数体」时**必须先剔除注释再匹配**(曾因注释含
|
||
`offsetParent` 导致假绿)。
|
||
|
||
### 4.6 三层审查的分工(建议明确,避免主管被 diff 淹没)
|
||
|
||
用户描述三层:agent 自审 → 主管复审 → 插件复核。
|
||
|
||
**建议定义清楚「主管复审」审什么**,否则主管会变成更贵的高级码农:
|
||
|
||
| 层 | 审什么 | 不审什么 |
|
||
|---|---|---|
|
||
| agent 自审 | 代码正确性、风格、实现细节 | — |
|
||
| **主管复审** | **这次的改动,判据够不够**:引入了哪些新失败方式?判据覆盖了吗?变异咬得住吗? | 逐行读 diff |
|
||
| 插件复核 | 机械执行正向 + 变异 | 语义判断 |
|
||
|
||
---
|
||
|
||
### 4.7 ★ 刀 3:conflict 预检与 base_sha [✅ 已完成 · 真实 git 实测]
|
||
|
||
#### 为什么这一刀必须做
|
||
|
||
并行开发(§4.4)让冲突从"可能"变成"必然":每分支一份独立 clone,
|
||
两棵树**互不可见**。冲突不会在开发时暴露,而是**全部堆到 merge 时爆发** ——
|
||
那时判据已经变绿、复审已经通过,却发现合不进去。
|
||
|
||
⇒ 必须在**提 PR 之前**就发现。
|
||
|
||
#### 做法:`git merge-tree --write-tree`(**完全不碰工作区**)
|
||
|
||
实测 git 2.53 的退出码语义:
|
||
|
||
| 退出码 | 含义 |
|
||
|---|---|
|
||
| `0` | 可干净合并 |
|
||
| `1` | 有冲突 |
|
||
| 其它 | 命令本身失败(**不能当成"无冲突"**)|
|
||
|
||
★ **它不写索引、不动 HEAD、不留 MERGE_HEAD** ⇒ 可以在任何时候安全地跑,
|
||
不需要 stash/checkout,也不会破坏正在进行的开发。这一条有判据钉住
|
||
(跑完预检后断言 HEAD 未变、无 MERGE_HEAD、工作区零改动)。
|
||
|
||
#### 三类结果必须分开(防静默降级)
|
||
|
||
| 结果 | 语义 |
|
||
|---|---|
|
||
| `Mergeable: true` | 检查过,可合并 |
|
||
| `Mergeable: false` + `Conflicts` | 检查过,有冲突(列出文件)|
|
||
| **`Skipped: true`** | **没检查**(目标分支本地不存在 / 非 git 仓库 / merge-tree 不可用)|
|
||
|
||
★ **`Skipped` 时绝不声称可合并** —— 把"没检查"当成"检查通过"是最危险的
|
||
静默降级,调用方会以为安全。有判据**逐条覆盖全部 Skipped 出口**
|
||
(初版只测了其中一条,于是另一条的变异不报红 —— 判据覆盖不全等于留洞)。
|
||
|
||
#### `base_sha` 的捕获时机
|
||
|
||
在 `runPrepareWorkdir` 里、**目录就绪后立刻**记 —— 必须在 agent 开始改之前。
|
||
agent 一旦提交,HEAD 就不再是"这条线从哪出发"。
|
||
|
||
★ **只记第一次**:重开同一分支时若覆盖基点,"这条线从哪出发"就永久丢失,
|
||
冲突判定与事后重放都失去依据。
|
||
|
||
#### 新增工具
|
||
|
||
| 工具 | 作用 |
|
||
|---|---|
|
||
| `commondev_conflict_check` | 提 PR 前预检可合并性(只读,`ParallelSafe`)|
|
||
| `commondev_parallel_status` | 盘点并行推进的分支线(按项目分组),派活前确认边界 |
|
||
|
||
#### ☆ 实测发现
|
||
|
||
**冲突解析有两条独立路径**,缺一不可:
|
||
|
||
1. `CONFLICT (content): Merge conflict in <path>` 行
|
||
2. `100644 <oid> <stage>\t<path>` 的 ls-tree 阶段行
|
||
|
||
前者覆盖常见情形,后者覆盖**不打印 CONFLICT 行**的冲突类型(如 rename/rename)。
|
||
实测:只保留路径 ①、把 ② 的判定前缀改坏,**"仅 ls-tree 形态"的判据会报红** ——
|
||
说明 ② 是真·载重路径,不是冗余。
|
||
|
||
#### 判据与变异
|
||
|
||
`conflict_parse_test.go` 8 条(含**真实 git 仓库**的端到端:可合并 → 合入后冲突
|
||
→ 工作区零影响);变异 6 条全部咬住(含 1 次暴露判据覆盖不全、1 次暴露
|
||
"冗余路径其实是载重路径")。
|
||
|
||
---
|
||
|
||
### 4.8 ★ 刀 4:判据与复核(正向 + 变异) [✅ 已完成 · 真实 go test 端到端]
|
||
|
||
#### 核心命题:插件 GREEN ≠ 改对了
|
||
|
||
设计 §4.5 的主张在本刀**落地并用真实数据验证**。我在 `/tmp` 复刻过最小场景:
|
||
|
||
```go
|
||
func Max(a, b int) int { if a > b { return a }; return b }
|
||
```
|
||
- 松判据只测 `Max(5,3) == 5`(漏了 `b>a`)
|
||
- 变异 `return b` → `return a`(**真 bug**)⇒ **判据仍全绿**
|
||
- 补一条 `Max(3,5) == 5` ⇒ 同一变异**立刻报红**
|
||
|
||
⇒ **没有反向阶段,就是在给「测试通过」这个信号不断充值信用,
|
||
直到它骗一次大的。**
|
||
|
||
#### 两阶段
|
||
|
||
| 阶段 | 做什么 | 不通过意味着 |
|
||
|---|---|---|
|
||
| **正向** forward | 跑判据 | 实现有 bug |
|
||
| **反向** mutate ★ | 把文件里 `old` 换成 `new`,期望判据**报红** | **判据太松**(该变异没被看见)|
|
||
|
||
#### 结论三档(**不合并成一个绿勾**)
|
||
|
||
| verdict | 含义 |
|
||
|---|---|
|
||
| `pass` | 正向全过 **且**全部变异被咬住 |
|
||
| `pass_with_gaps` | 正向过,但**有变异未被咬住** 或**有判据没定义变异规则** |
|
||
| `fail` | 有判据未通过 |
|
||
|
||
★ `pass_with_gaps` **不是"通过"** —— 它是「判据没覆盖到」。
|
||
把它说成通过,就是本刀要消除的那种"信号充值"。
|
||
|
||
#### 新增工具
|
||
|
||
| 工具 | 作用 |
|
||
|---|---|
|
||
| `commondev_test_define` | 主管定义判据(**幂等**:同定义重复登记不改动);必须同时给 `mutations` |
|
||
| `commondev_test_list` | 列出判据(含变异条数 —— **为 0 的是复核缺口**)|
|
||
| `commondev_review_submit` | 跑复核(后台作业:判据可能含编译/压测,超 60s 工具上限)|
|
||
| `commondev_review_result` | 取结论与历史(含逐条证据)|
|
||
|
||
#### ☆ 实现要点(每条都对应一个可致错的点)
|
||
|
||
| 点 | 为什么 |
|
||
|---|---|
|
||
| **必须 `-count=1`** | Go 默认缓存测试结果。实测:变异后若命中缓存,测试**根本没跑**,"报红"无从谈起 |
|
||
| **变异必须恢复文件** | `defer` 保证 panic 也恢复;且恢复后**校验内容一致**——恢复不回去比变异本身危险(agent 会基于坏代码继续改)|
|
||
| **`old` 必须恰好出现一次** | 出现 0 次 ⇒ 变异无效,却被误报成"判据太松"(**假警报**);出现多次 ⇒ 可能改错地方,却报"咬住"(**假通过**)|
|
||
| **通过由退出码判定** | 不是看输出里有没有 `PASS`(输出可被伪造/污染)|
|
||
| **固定判据顺序** | map 遍历随机 ⇒ 报告不可对照 |
|
||
|
||
#### 判据与变异
|
||
|
||
`criteria_test.go` 10 条 + `e2e_criteria_test.go` 2 条(**真实项目 + 真实 go test**);
|
||
变异 5 条对**复核机制本身**做变异(元层级),全部咬住 ——
|
||
包括「变异后不恢复文件」「变异未咬住却报 ok」「无变异规则却给 pass」。
|
||
|
||
#### ⚠️ 尚未做(原计划的"模板库"部分)
|
||
|
||
设计 §6.4 原写「测试模板库(压力/接口/编译)」,本次落地的是**机制**
|
||
(判据定义 + 复核执行),**预置模板库**仍未做。理由:
|
||
机制不成立时模板是摆设;现在机制已用真实数据证明,模板可以后续按需补。
|
||
|
||
---
|
||
|
||
## 5. 与现有系统的关系
|
||
|
||
| 现有物 | 关系 |
|
||
|---|---|
|
||
| `plugindev` 示例插件 | commondev 的**起点**(可复用其 hmapdev 调用、workspace 管理) |
|
||
| `plugindev` 的 `projects` 工具 | 可参考其「列工程 + 构建产物」的形态 |
|
||
| `core.agent.workdir` | 全局单值 fallback,**不要**把它当 per-agent 机制用(§3.8) |
|
||
| `spawn_child` | 内核工作式轻量子,**与本需求无关**(§3.7) |
|
||
| `a2a` / `acp` 插件 | 现无 cwd,需扩建或绕过(§3.4) |
|
||
| Gitea 1.24.2(本机) | **建议作为第一刀的唯一平台**(见 §6.2) |
|
||
| `tracker`(changeset/回滚) | 已有的文件系统变更追踪,与 git 分支台账**无关**,勿混 |
|
||
|
||
---
|
||
|
||
## 6. 关键决策建议(未拍板,供接手者与用户确认)
|
||
|
||
### 6.1 拉起 agent:走 CLI 家族,不走 A2A/ACP [本节已由续篇修正]
|
||
|
||
> **⚠️ 修正(2026-10-13):** 本节初稿只论证了 **cwd** 一个维度就下了结论,
|
||
> **论据不全**。用户明确要求「投递必须落到**同一个会话**,不能前一个没做好就投给新会话」,
|
||
> 而 `pi -p` 每轮一个进程,**无法保证**这一点(实测:漏传 `--session-id` 会静默开新会话;
|
||
> 并发传同一个 id 会写坏会话)。
|
||
>
|
||
> 正确的两轴(会话粒度 × cwd 由谁承担)与形态取舍,见续篇
|
||
> **`docs/zh/commondev-agent-runtime-design.md` §1.2 / §3**。
|
||
> 本节保留,只作为「A2A/ACP 为何被排除」的论据(该结论本身没变)。
|
||
|
||
| 方案 | 优点 | 缺点 |
|
||
|---|---|---|
|
||
| **`pi -p`(CLI)** | 进程 cwd 即工作目录(**已实测**);一次性进程,天然按任务隔离;零协议改动 | **无法保证续投同一会话**(见上);每次冷启动;无中途纠偏 |
|
||
| **`pi --mode rpc`(常驻)** | 同 cwd;同会话续投;`steer`/`follow_up` 可中途纠偏;`get_state` 可断言 session 未变 | 要实现/复用 RPC 客户端;stdout 必须持续读 |
|
||
| **Node SDK(进程内)** | 最贴身;`createAgentSession({cwd})` 可控 cwd 与会话 | 需 Node 宿主 |
|
||
| `dsh headless` | 同上 | 本机凭据失效,需先修 |
|
||
| 扩建 A2A | 复用现有插件 | 要改扩展 + payload;**参数里没有 cwd**(§3.4) |
|
||
| 扩建 ACP | 同上 | 同上;且 ACP 的 session 模型本就假设长驻,加 cwd 是**违背其设计意图** |
|
||
|
||
**修正后的建议**:**内嵌 sidecar 为默认形态**,远程 SSH 由配置切换
|
||
(两形态共用同一 sidecar,差异只在传输层)。`pi -p` 仅作侧车不可用时的降级,
|
||
**不是正式形态**。A2A/ACP 依然排除(「无 cwd」这条事实没变),
|
||
排除理由与本需求无关:它们是**常驻 peer** 的模型,而本需求是**按工程对话拉起**。
|
||
|
||
**选型已定 [2026-10-13 用户拍板]**:**默认内嵌(进程内 pi 实例)**,
|
||
配置可切到**远程 SSH**;远端同样**创建目录并在该目录拉起 pi 实例**。
|
||
远端环境缺失时**询问用户、不得擅自安装**(见续篇 §3.4)。
|
||
|
||
### 6.2 第一刀只做一个平台、一个项目、一个任务
|
||
|
||
理由:这套机制有**至少四个独立的失败面**(凭据 / 拉起 / 判据 / 合入),
|
||
一次全上会无法归因。先用 Gitea(本机就有,API 可控)跑通**一条真实链路**:
|
||
|
||
```
|
||
登记项目 → mirror → 开分支 → 拉起 pi -p(cwd=工作目录)
|
||
→ agent 改 → 主管定义判据 → 插件跑「正向 + 变异」
|
||
→ 提 PR → 主管合入
|
||
```
|
||
|
||
**这一刀的目的不是功能完整,是证明「假绿检测」这一环真的能工作。**
|
||
|
||
### 6.3 权限模型:让「必须有主管复审」成为机制而非礼貌 [✅ 已完成 · 真实 Gitea 实测]
|
||
|
||
若执行 agent 所用的凭据能 push 主干,则「主管复审」只是**流程约定**——想绕过随时能绕过。
|
||
|
||
**机制**(已在真实 Gitea 1.24.2 上逐条实测):
|
||
|
||
| 动作 | 执行侧凭据 | 主管侧凭据 |
|
||
|---|---|---|
|
||
| push `main` | ❌ `pre-receive hook declined` | (不需要)|
|
||
| push `feat/*` | ✅ | ✅ |
|
||
| 开 PR | ✅ | ✅ |
|
||
| **自合 PR(无审批)** | ❌ `Does not have enough approvals` | ❌ |
|
||
| 审批(带 commit_id)| ❌(角色不符) | ✅ |
|
||
| 合 PR(有审批)| — | ✅ |
|
||
|
||
★ **限制的真身是平台的分支保护**(`enable_push:false` + `required_approvals≥1`),
|
||
不是插件里的判断 —— 插件在信任边界外,改一行代码就能绕过;平台的
|
||
pre-receive hook 不能。这是刀 2 与「流程约定」的本质区别。
|
||
|
||
**实现落点**:
|
||
|
||
| 件 | 作用 |
|
||
|---|---|
|
||
| `Account.Role`(`executor`/`supervisor`) | 标注凭据能做什么;空值按 supervisor(兼容老数据)|
|
||
| `commondev_protect_branch` | 幂等建立分支保护(只改必要项,不反复改用户仓库)|
|
||
| `commondev_pr_approve` / `commondev_pr_merge` | 主管侧动作,按角色选凭据 |
|
||
| `accountForRole` | 角色不符时**明确报错**(而非让平台报一个看不懂的错)|
|
||
|
||
**`defaultProtection()` 的四条**(每条堵一条绕过路径,均有判据钉住):
|
||
|
||
| 字段 | 值 | 堵住的绕过 |
|
||
|---|---|---|
|
||
| `enable_push` | `false` | 执行侧直接推 main |
|
||
| `required_approvals` | `≥1` | 无人审批也能合 |
|
||
| `block_admin_merge_override` | `true` | **admin 绕过**(主管账号的后门)|
|
||
| `dismiss_stale_approvals` | `true` | 审批后追加提交偷换内容 |
|
||
|
||
#### ☆ 实测踩到的三个 API 坑(都不是文档里写着的)
|
||
|
||
| 坑 | 现象 | 真因 |
|
||
|---|---|---|
|
||
| **`event` 必须是 `APPROVED`** | 传 `APPROVE` **返回 200 不报错**,但 review 落成 `PENDING` ⇒ 合并报 "Does not have enough approvals"(看起来像权限问题)| 枚举少了个 D |
|
||
| **审批必须带 `commit_id`** | 不带时同样落成 `PENDING`(与 event 是两个独立条件)| 审批要绑定具体提交 |
|
||
| **`required_approvals` 也管 admin** | admin 没有绕过审批的特权 | 这是**正确的** —— 否则"复审"对 admin 形同虚设 |
|
||
|
||
附带(正确行为但要知):`REQUEST_CHANGES` 会**阻断**合并,即使同时有 `APPROVED`。
|
||
|
||
#### 判据与变异
|
||
|
||
`protection_test.go` 11 条;变异 9 条全部咬住(含 1 次暴露判据测错层次 ——
|
||
只测 `platformMessage()` 没测 `MergePR` 是否调用它)。
|
||
|
||
### 6.4 落地顺序
|
||
|
||
| 刀次 | 内容 | 证明什么 |
|
||
|---|---|---|
|
||
| **0** | **修「能拉起 agent」**(§3.6) | 前提是否成立 |
|
||
| **1** | 单平台单项目:mirror → 分支 → 拉起 → 判据+变异 → PR | 机制成立,假绿可检测 |
|
||
| 2 | 接 Gitea 分支保护 + 执行/主管双凭据 | ✅ **已完成**(真实 Gitea 1.24.2 实测,见 §6.3)|
|
||
| 3 | 多项目 + 并行 + conflict 预检 | ✅ **已完成**(真实 git 实测,见 §4.6)|
|
||
| 4 | 判据与复核(正向 + 变异) | ✅ **已完成**(真实 go test 端到端,见 §4.8)|
|
||
| **S** | **★ 内核安全模块(Secret Firewall)** —— 见
|
||
`docs/zh/kernel-secret-firewall-design.md`。
|
||
插在 **provider 层**(已核:构造 `CompletionRequest` 有 7 处,task 层拦不全)。
|
||
**它先行的理由**:插件把密钥明文直接投给 LLM(用户拍板)—— **没有这道闸,
|
||
密钥就真的发出去了**。而 2/3/4 刀都要用凭据(push / PR / 测试),
|
||
它们建立在“提交密钥是安全的”这个前提上 ⇒ **安全模块是它们的前提**。 | 凭据不泄露 |
|
||
|
||
> **第 S 刀的位置说明**:它不排在 2/3/4 之后,而是**与刀 2 并行且先行**。
|
||
> 这与刀 0 同理:**前提不成立时,不要继续构建上层**。
|
||
|
||
> **第 4 刀为什么放最后**:它最容易写,也最容易变成摆设。先让「变异能咬住」
|
||
> 这一环成立,模板才有意义。
|
||
|
||
---
|
||
|
||
## 7. 安全设计(必读)
|
||
|
||
### 7.1 凭据存放 [风险]
|
||
|
||
**现状**(§3.2):`config_<plugin>` 明文、`config.db` 644。
|
||
commondev 的凭据是**能 push 用户所有仓库**的凭据,性质完全不同。
|
||
|
||
建议:
|
||
|
||
1. **不放 `config.db`**。放独立文件(如 `<插件数据目录>/credentials.json`),权限 `0600`,
|
||
**启动时校验权限,不对就拒绝启动**(本仓纪律:响亮失败,不静默降级)
|
||
2. **优先 SSH key 而非 PAT**:key 可按平台单独生成/吊销,不随账号密码轮换失效
|
||
3. **每平台用独立、最小授权的凭据**(GitHub fine-grained PAT 支持单仓授权),
|
||
**不要一个 token 通吃**
|
||
4. 接 `git push` 时走 **credential helper / 环境变量注入**,**不要**写进 git config 文件
|
||
|
||
#### ★ 变量填充引用 [已定 2026-10-14 · 用户要求]
|
||
|
||
用户明确要求:「gitea / gitcode / github 等主流系统的 ackey 保存与**通过变量填充引用**」。
|
||
|
||
**核心设计:按名字引用 + 值可写 `${ENV_VAR}`**
|
||
|
||
```
|
||
commondev_account_add(name="gitea-local", platform="gitea",
|
||
token="${GITEA_TOKEN}", ← ★ 引用,不是明文
|
||
base_url="http://192.168.2.106:3000")
|
||
|
||
之后一切操作只带**凭据名**:
|
||
commondev_branch_open(..., account="gitea-local") ← 参数里永远不出现 secret
|
||
```
|
||
|
||
**为何要变量填充**(三个真实好处):
|
||
|
||
| 好处 | 说明 |
|
||
|---|---|
|
||
| 明文不落盘 | 文件里只有 `${GITEA_TOKEN}` |
|
||
| 环境可切换 | 同一份配置,dev/prod 换环境变量即可 |
|
||
| 轮换不需改数据 | 改环境变量 + 重启即可 |
|
||
|
||
**两条不可逆的纪律**:
|
||
|
||
1. **变量解析不到值 ⇒ 响亮报错**,绝不静默当空串。
|
||
后者会退化成匿名访问,表现成 **401** 而非“凭据没配” —— 极难排查。
|
||
实测:`${NONEXISTENT_VAR_ZZZ}` → 报“凭据引用的环境变量未设置: …”。
|
||
2. **secret 绝不进任何返回值/日志**:`account_list` 只报 `token_ref`(引用本身不是秘密,
|
||
对排查有用)与 `token_set` 布尔。实测:明文 token 在日志中出现 **0 次**。
|
||
|
||
**平台形状差异**(必须抽象,不能一个头部走天下):
|
||
|
||
| 平台 | API 前缀 | 认证头 |
|
||
|---|---|---|
|
||
| Gitea | `<base>/api/v1` | `Authorization: token <T>` ← **不是 Bearer** |
|
||
| GitHub | `https://api.github.com` | `Authorization: Bearer <T>` |
|
||
| GitCode | `https://api.gitcode.com/api/v5` | `Authorization: Bearer <T>` |
|
||
|
||
未知平台**必须报错**(不猜默认形状)。
|
||
|
||
**凭据可用性要真验**:`account_test` 调平台的 `/user` 端点,不是只看字符串非空。
|
||
|
||
### 7.2 临时目录的凭据残留 [风险]
|
||
|
||
任务跑完删目录后,**clone 里可能仍留着 token**(remote URL 带 token 是常见做法,
|
||
`git remote -v` 就能读出来)。
|
||
|
||
**规矩**:clone 出来的 remote URL **不带凭据**;push 时临时注入
|
||
(Gitea/GitHub 均支持 `http.extraheader` 或 `GIT_ASKPASS` 一次性注入)。
|
||
这样临时目录删掉就是真删掉。
|
||
|
||
建议 `commondev_workdir_clean` 内置一道**残留检查**(扫 remote URL / config / 已知凭据指纹)。
|
||
|
||
### 7.3 插件自身不是沙箱
|
||
|
||
本仓已知:插件是编译型 Go 子进程,能做 `os/exec`、读写文件。commondev 持有高价值凭据,
|
||
**它自己的权限边界需要单独评估**(用户在主机的进程隔离、文件权限是否足够)。
|
||
本文不展开,但接手者应至少确认:commondev 的数据目录权限、以及宿主对插件进程的隔离手段。
|
||
|
||
---
|
||
|
||
## 8. 未决问题(需用户拍板)
|
||
|
||
### 8.1 「独立仓库」还是「worktree」?[已解决 2026-10-13]
|
||
|
||
**用户拍板:独立仓库(本地 clone)。** 目录 `program/<hash>-<name>`(`name` = 仓库名),
|
||
**合入后直接删除目录**。
|
||
|
||
理由:worktree 的 `rm -rf` 不彻底(`.git/worktrees/` 残留,需 `prune`),
|
||
而 `--reference` 有外部依赖(mirror 被清理即损坏)——**两者都不满足「删掉即彻底」**。
|
||
实测与取舍见 §4.3。
|
||
|
||
**代价与补偿**:失去 worktree 的「同分支不可双挂」保护 ⇒ 台账层补**唯一性闸**(§4.3)。
|
||
|
||
### 8.2 主管(主 agent)自己的模型可用性
|
||
|
||
主管也要调 LLM(拆任务、定义判据、复审)。若主管所用模型正是 §3.6 里失效的那些,
|
||
整套机制同样停摆。**需要为「主管」单独确认一个稳定可用的 provider/model。**
|
||
|
||
### 8.3 判据由谁写、写在哪
|
||
|
||
用户说「你(主管)定义单元测试」。但判据的**落地形态**未定:
|
||
- 写进被测仓库(成为仓库的一部分,随 PR 一起走)?
|
||
- 还是留在 commondev 台账里(`criteria` 表)?
|
||
|
||
两种各有取舍:前者可被仓库自身复用,后者与实现解耦但要维护两处。
|
||
**建议**:留在台账,但把生成的脚本**落盘到工作目录内**执行,并在 PR 描述里附判据摘要。
|
||
|
||
### 8.4 冲突与依赖
|
||
|
||
若并行任务之间有**依赖**(A 改接口、B 用接口),worktree/独立 clone 会**放大**问题:
|
||
两棵树互不可见,A 的改动 B 编译不过。
|
||
|
||
用户需要明确:**并行的是「互不相干的模块」还是「一个功能的几个部分」?**
|
||
- 前者 → 本设计正合适
|
||
- 后者 → 会集中在 merge 时爆发,建议**先拆分边界**(谁拥有哪些文件)再谈并行
|
||
|
||
### 8.5 责任边界
|
||
|
||
用户说「以后只跟主管对接」。那么当判据打勾、PR 合了、线上仍出问题时,责任如何划分?
|
||
这决定**判据里必须包含什么**:若主管负责,则光有单元测试不够,
|
||
需要纳入**可观测性/回归判据**(真跑一遍看真事件流,而非只看测试绿)。
|
||
|
||
---
|
||
|
||
## 9. 接手者请从这里开始
|
||
|
||
### 第一步(必做):验证前提是否成立
|
||
|
||
写一个最小探针,回答一个问题:**「能否稳定拉起一个 agent 并让它读到指定目录?」**
|
||
|
||
要求:
|
||
- 在临时目录放 marker 文件,拉起 `pi -p`,要求输出 marker 内容
|
||
- **跑 20 次,统计成功率**(不要只跑一次成功就下结论)
|
||
- 记录每次使用的 provider/model,据此**枚举可用组合**
|
||
- 若成功率低,先解决 §3.6,**不要继续上层设计**
|
||
|
||
### 第二步:只做 §6.2 的第一刀
|
||
|
||
用 Gitea。链路:mirror → 分支 → 拉起 → 判据 + **变异** → PR。
|
||
**重点验证「变异能咬住判据」**——故意写一个太松的判据,确认变异阶段能把它驳回。
|
||
|
||
### 第三步:把本篇文档的每一条事实重新核对
|
||
|
||
本文的 [已核] 项都有命令可复现,见 §10。**若发现与本文不符,先更新本文再动手。**
|
||
|
||
---
|
||
|
||
## 10. 自检清单(逐条可复现)
|
||
|
||
接手者应能执行以下命令并得到与本文一致的结论:
|
||
|
||
```bash
|
||
# §3.1 平台
|
||
git -C /home/program/TrueAgent remote -v
|
||
curl -s http://192.168.2.106:3000/api/v1/version # 期望 {"version":"1.24.2"}
|
||
|
||
# §3.2 凭据现状(注意:不要打印完整 token)
|
||
ls -l /home/newqqagent/config.db # 期望 644
|
||
sqlite3 /home/newqqagent/config.db "select key from config_vikunja;" # 含 token
|
||
|
||
# §3.4 拉起 agent 的 cwd 行为
|
||
mkdir -p /tmp/probe-a && echo AAA > /tmp/probe-a/marker.txt
|
||
cd /tmp/probe-a && pi -p --provider <可用provider> --model <可用model> \
|
||
"执行 pwd 和 cat marker.txt,只输出这两行原始内容"
|
||
# 期望输出 /tmp/probe-a 与 AAA
|
||
|
||
# §3.4 a2a/acp 无 cwd
|
||
grep -n "agent_url\|query\|session_id" \
|
||
/home/program/TrueAgent/third_party/homeagent-sdk/example/a2a/plugin.go
|
||
grep -n "cwd" /root/.pi/agent/extensions/pi-acp/index.ts # 期望无结果
|
||
|
||
# §3.5 端口归属
|
||
ss -tlnp | grep -E "14010|14011" # 期望 pid=819 pi-web-sessiond
|
||
|
||
# §3.8 core.agent.workdir 是全局单值
|
||
grep -n "core.agent.workdir" /home/program/TrueAgent/internal/config/registry.go
|
||
# 期望 Type: "string"
|
||
|
||
# §3.7 spawn_child 参数
|
||
grep -n "max_turns\|task" /home/program/TrueAgent/internal/agent/core/spawn.go | head
|
||
|
||
# §4.5 变异能咬住判据的现成范例
|
||
cd /home/program/TrueAgent
|
||
go test -count=1 -run TestStageFrameFieldsMatchServer ./internal/plugins/webui/
|
||
grep -n "phase,omitempty" cmd/waiter/state.go # 判据匹配的是完整 tag
|
||
```
|
||
|
||
---
|
||
|
||
## 12. ★ 架构变更:平台知识进 skill(2026-10-15)
|
||
|
||
> **本节修正本文 §4.1。** 原清单里的**平台专有工具**不再实现在插件里;
|
||
> 其余机制(台账、工作目录、pi 会话与通道、判据与变异复核、冲突预检、
|
||
> 凭据保存与安全执行)**保留**。
|
||
|
||
### 12.1 用户拍板的边界
|
||
|
||
> 「我说的是 **git 部分 skill 化**啊,其他部分还是需要插件的」
|
||
|
||
| 层 | 负责 |
|
||
|---|---|
|
||
| **插件 commondev** | 项目台账、工作目录(mirror + 独立 clone)、pi 会话与通道、判据与变异复核、冲突预检、`enc_*` 凭据保存与安全执行 —— **40 个工具** |
|
||
| **skill** | **各 git 托管平台的认证头、API 端点、PR 与分支保护流程** |
|
||
|
||
### 12.2 移出插件的部分(10 个工具 → skill)
|
||
|
||
| 原工具 | 现在怎么做 |
|
||
|---|---|
|
||
| `commondev_account_add/list/test/remove` | `enc_store` / `enc_list` + skill 里的认证头对照表 |
|
||
| `commondev_repo_search` | `enc_shell` + `curl <API>/repos/search?q=` |
|
||
| `commondev_pr_open` / `pr_status` | `enc_shell` + `curl -X POST .../pulls` |
|
||
| `commondev_pr_approve` / `pr_merge` | `enc_shell` + `curl .../pulls/{n}/reviews`、`/merge` |
|
||
| `commondev_protect_branch` | `enc_shell` + `curl -X POST .../branch_protections` |
|
||
|
||
删除的文件:`platform.go`、`protection.go`、`pr_workflow.go`(约 1270 行)。
|
||
`credentials.go` 保留但**去掉了平台语义**(`platformSpec` / `Role` / `BaseURL`)。
|
||
|
||
### 12.3 为什么平台部分不该在插件里
|
||
|
||
1. **每接一个托管平台都要改 Go 代码** → 重编译 → 重打包 → 重启服务。
|
||
而平台知识(哪个端点、什么认证头)本该和文档一起演进。
|
||
2. **平台形状被硬编码**:`platformSpec` 的 switch 每加一家就长一截,
|
||
**未列出的平台直接报错**——实测 GitLab / Gitee / Bitbucket / Azure DevOps 全不支持。
|
||
而 agent 用 `curl` 本来什么平台都能打。
|
||
3. **工具面膨胀**:50 个工具让模型选择成本上升。
|
||
|
||
⇒ 平台知识进 skill(改文档**立即生效**);插件只保留机器必须保证的机制。
|
||
|
||
### 12.4 保留的纪律(没变)
|
||
|
||
- 凭据不进 `config.db`(0644 全局可读)、不进 remote URL、不进命令行参数;
|
||
- **没有「取回明文」的接口**:只能被 `@{别名}` 引用(S1);
|
||
- 变量库不自动淘汰,泄漏控制靠主动删除;
|
||
- `enc_shell` 三条保障:明文不进 argv(stdin)、输出脱敏、超时;
|
||
- 「不要把测试通过当作改对了」——复核必须含**变异**阶段(§4.5 主张仍成立)。
|
||
|
||
### 12.5 判据(钉住不回退)
|
||
|
||
`alignment_test.go` 新增两条:
|
||
|
||
- `TestPlatformToolsAreNotInPlugin` —— 那 10 个平台工具不得再注册;
|
||
- `TestNoPlatformSpecificCodeInPlugin` —— 源码里不得再出现 `platformSpec` /
|
||
`AuthHeader:` / `DefaultAPI` / `RoleExecutor` 等平台专有符号
|
||
(**排除注释**:说明"已移出"是允许的)。
|
||
|
||
**为何要判据而不只是文档**:文档说了还会有人(包括将来的我)图省事加回去,
|
||
而那种回退**功能上看起来完全正常** —— 只是把可移植性换成了硬编码。
|
||
|
||
---
|
||
|
||
## 13. 功能完备性核对(2026-10-15)
|
||
|
||
逐章节对照设计文档核对实现,**19/19 通过**(脚本化核对,非人工目测):
|
||
|
||
| 设计条目 | 实现 |
|
||
|---|---|
|
||
| §4.1 工具面(扣除已移出的 10 个平台工具) | ✅ 40 个工具 |
|
||
| §4.2 台账 schema(Project/Branch) | ✅ |
|
||
| §4.3 mirror + 本地 clone(硬链接) | ✅ |
|
||
| §4.3 ⚠ 唯一性闸(同名未结分支拒绝) | ✅ |
|
||
| §4.3 目录名 `<hash>-<name>` | ✅ |
|
||
| §4.4 可并行(每分支独立 clone) | ✅ |
|
||
| §4.5 复核两步(正向 + 变异) | ✅ |
|
||
| §4.7 `base_sha` 首次捕获 | ✅ |
|
||
| §4.7 `merge-tree` 预检(不碰工作区) | ✅ |
|
||
| §4.7 `Skipped` 与 `Mergeable` 分开 | ✅ |
|
||
| §4.8 三档 verdict | ✅ |
|
||
| §4.8 变异必须唯一匹配 | ✅ |
|
||
| §7.2 凭据残留检查 | ✅ |
|
||
| §7.2 remote 不带凭据 | ✅ |
|
||
| 续篇 §2.2 先 session 后通道 | ✅ |
|
||
| 续篇 §2.5 投递走内核 output 门 | ✅ |
|
||
| 续篇 §3.6.3 直接写 pi 配置文件 | ✅ |
|
||
| 续篇 §3.6.5 不静默回退宿主凭据 | ✅ |
|
||
| S1 无「取回明文」接口 | ✅ |
|
||
|
||
### 13.1 LLM 源端到端(本轮新增)
|
||
|
||
新增 `llmsource_e2e_test.go`(5 条判据),把「内核源 → 插件导入 → pi 配置 →
|
||
**真实调用成功**」整链收口:
|
||
|
||
| 判据 | 层次 | 验证 |
|
||
|---|---|---|
|
||
| `TestLLMSourceImportProducesValidPiConfig` | 纯逻辑 | models.json 形状、`openai`→`openai-completions` 映射、contextWindow 透传、0600 权限、defaultProvider |
|
||
| `TestLLMSourceImportRejectsUnknownAdapter` | 纯逻辑 | 未知 adapter **必须报错**(猜默认会让 pi 用错协议,现象是「模型不回复」)|
|
||
| `TestLLMSourceImportFailsWithoutKernelSources` | 纯逻辑 | 无源时报错,不生成空配置 |
|
||
| `TestLLMSourceImportFromRealKernelConfig` | **真机** | 读生产 `config.db` 的真实字段形态,3 个源全部导入成功 |
|
||
| `TestLLMSourceRoundTripAgainstRealEndpoint` | **真机** | 从**导入产物**(而非测试变量)读回地址与 key,真实 HTTP 调用成功 |
|
||
|
||
**真机结果**:
|
||
|
||
```
|
||
✅ 真实内核配置导入成功:3 个源,默认=llmsproxy
|
||
✅ 端到端通过:内核源 → 插件导入 → 用磁盘配置真实调用成功
|
||
```
|
||
|
||
**为何必须端到端**:导入逻辑正确 ≠ pi 能用。中间三层各自可能出错
|
||
(字段映射 / adapter→api 名 / 默认源写入),任何一层错了现象都是
|
||
「pi 起不来 / No API key found」,而单元判据全绿。
|
||
|
||
**变异验证(4 条全咬住)**:
|
||
|
||
| 变异 | 结果 |
|
||
|---|---|
|
||
| adapter 映射成错的 api 名 | ❌ 报红(纯逻辑判据)|
|
||
| base_url 不写入 | ❌ 报红 |
|
||
| **api_key 不写 auth.json** | ❌ **真机 e2e 报红**(真实调用失败)|
|
||
| 未知 adapter 猜默认值 | ❌ 报红 |
|
||
|
||
复现命令:
|
||
|
||
```bash
|
||
cd third_party/homeagent-sdk/example/commondev
|
||
K=$(sqlite3 /home/newqqagent/config.db "select value from config where key='core.llm.api_key';")
|
||
HA_E2E_LLM=1 HA_E2E_API_KEY="$K" HA_E2E_REAL_DB=/home/newqqagent/config.db \
|
||
go test -count=1 ./...
|
||
```
|
||
|
||
### 13.2 判据与变异总览
|
||
|
||
| | 数量 |
|
||
|---|---|
|
||
| 判据 | **62** |
|
||
| 变异(累计验证咬住) | 30+ |
|
||
| 工具 | 40 |
|
||
| 非测试代码行数 | 5437 |
|
||
|
||
---
|
||
|
||
## 14. 历史实现审计(2026-10-15 早)
|
||
|
||
本节是对原始设计清单的逐条回读结果;不要仅依据文档顶部的历史状态判断实现状态。
|
||
|
||
### 14.1 已实现并有测试/实测
|
||
|
||
| 设计项 | 实现/证据 |
|
||
|---|---|
|
||
| 刀 0/1:项目、分支、独立 clone、sidecar、通道、续投 | `plugin.go` / `sidecar.go`;branch/session/channel 端到端测试 |
|
||
| 刀 2:双角色凭据、分支保护、审批、合并 | `protection.go`;真实 Gitea 1.24.2 矩阵 + 变异 |
|
||
| 刀 3:`base_sha`、`merge-tree --write-tree`、Skipped 分离 | `conflict.go`;真实 git 仓库 + 变异 |
|
||
| 刀 4:判据、正向、变异、三档 verdict、后台复核 | `criteria.go`;真实 `go test` 端到端 + 机制元变异 |
|
||
| 原 §4.1 工具面 | 项目/分支/工作目录/agent/PR/判据/pi 配置/MCP/仓库搜索工具均已注册 |
|
||
| Secret Firewall 与配置加密 | `internal/security`、`internal/config`;race 与三层回归 |
|
||
| E-4 生产密钥注入 | `deploy/homeagent.service` 的 `LoadCredential`;真实 systemd 262 实测 tmpfs/0400/不进 environ |
|
||
|
||
### 14.2 设计文字与实现的取舍
|
||
|
||
- `commondev_agent_send`:续篇 §2.5 已明确废除;发送继续走内核原生 `output_send__...`,不重复造工具。
|
||
- `commondev_test_run` / `commondev_test_mutate`:早期清单的两个单阶段入口已保留;`commondev_review_submit` 是两阶段一键入口。
|
||
- `commondev_pi_plugin_*` 与细化稿的 `commondev_pi_package_*`:两套命名均注册为兼容别名;底层调用同一套 `pi install/remove/list/update`。
|
||
- `commondev_project_open`:续篇早期别名已补,底层复用 `project_add`。
|
||
- skill 双投不是运行时插件行为:主管侧 skill 由 HologramAgent 发布,执行侧 skill 由工作目录/发布包注入;插件不擅自把代码写进用户全局目录。
|
||
|
||
### 14.3 仍明确未实现的设计项
|
||
|
||
这些不是本次审计漏掉的隐藏功能,而是设计文档已标出的未决/后续项:
|
||
|
||
- 远端 SSH runtime(当前仅 local);
|
||
- 预置测试模板库(当前完成的是判据/变异机制,不是 unit/interface/stress/build 模板集合);
|
||
- U4 `steer` 语义降级:当前接受 pi 的插话语义,不伪称为内核式抢占恢复;
|
||
- U8 的“随包锁定独立 pi 二进制”仍取决于发布包;当前 agent_up 会回显 sidecar 可解析到的 `pi_version`,无法解析时显式警告,不编造版本。
|
||
|
||
审计结论:**原始设计的核心机制已实现,但不能宣称所有后续项全部完成。** 本节列出的项目是剩余真实边界。
|
||
|
||
|
||
|
||
1. **不要把「测试通过」当作「改对了」**。本文 §4.5 的变异验证不是可选项,
|
||
它是这套机制唯一能防住假绿的手段。本仓当天因此浪费过一整轮排查。
|
||
|
||
2. **不要用提示词层面的软约定替代机制**。前序 worktree 讨论里
|
||
「在任务描述里写明工作目录」就是这一类——它靠模型自觉,没有任何保证。§3.8 已记录该教训。
|
||
|
||
3. **前提不成立时,不要继续设计上层**。§3.6 的「拉起 agent 不工作」如果没解决,
|
||
后续所有设计都是纸面的。**先让它能跑,再让它跑得好。**
|