Skip to content

仓库面板 ​

大多数智能体工作的起点,都是有人读完一个 issue,再把它重新敲进提示词里。仓库面板省掉了这一步:它把某个文件夹的 GitHub、GitLab 或 Gitea issue 和 pull request 列在 Codeg 里,并把你挑中的那一条作为待办任务交给智能体——它自己的 worktree、它自己的会话,以及在任何东西落地之前那道同样的审阅关卡。

这个功能在 0.27.0 中带着 Beta 标记发布,而这个标记在 0.30.0 摘掉了 —— 因为面板此时已经能走完整个闭环:分标签页读一个改动、在上面评论、开关 issue、以及合并,全程不需要浏览器。Gitea 在 0.30.4 加入,并顺带带上了 Forgejo:Forgejo 是 Gitea 的一个分支,提供同一套 /api/v1,所以能和其中一个对话的客户端也能和另一个对话,没有单独的设置项。

它在整体中的位置

这个面板是通往「待办任务」的一道前门,而不是一套并行的系统。它创建的一切都是看板上普通的任务,只是多记录了一个事实:它来自哪一个工作项。正是这份来源信息解锁了最后的交付步骤——也正是它让自动合并规则有所不同。

打开它 ​

仓库面板位于左侧边栏「待办任务」的下方,也在状态栏的快捷操作中的导航分组里。和「自动化」「待办任务」一样,它会接管主区域,右上角有一个返回箭头带你回到对话。

它打开时,窗口右上角的按钮簇会多出两个按钮:刷新——它重新加载整个列表,而不是缩小列表范围——以及面板设置。在真正加载出一个仓库之前,刷新是灰的;设置则永远可用,因为总有一份全局配置可以修改。

开始之前,先有一个账户 ​

这个面板读取的凭据和 git 用的是同一份,在设置 → 版本控制下。选中一个项目文件夹,Codeg 就会去看它的 origin 远端:

  • 主机三者都不是——比如 Gitee 或 Bitbucket 远端。面板会说明只支持 GitHub、GitLab 和 Gitea并点出主机名,而不是回退到 GitHub、再把错误 API 的答复原样报出来 —— 从前你看到的是「gitee.com 没有 GitHub 账户」或者一段生的 API 失败。添加账户仍然提供,因为那是自建实例的另一条入口。
  • 有远端,但没有该主机的账户——空状态会点名提供商和主机,并给出添加账户,直接带你去设置页面。

得出这个结论靠的是探测,而不是猜测。名字认不出来的主机会被直接问一句「你是什么」,从最便宜的问题开始:

  1. Gitea,问 GET {origin}/api/v1/version。它排在最前,是因为这里只有它既不需要凭据、答案又落在响应体里:每一台 Gitea 都公开提供这个端点,并回一个版本对象。一个对什么都回 200 的笼统网关造不出那个形状,所以排除它不用额外花一次请求。GitHub Enterprise(/api/v3)和 GitLab(/api/v4)则根本不在 /api/v1 上挂任何东西。
  2. GitLab,问 GET {origin}/api/v4/version —— 带着令牌时 GitLab 回 200,不带时回 401,而 GitHub Enterprise 会回 404。这两种回答单独都不作数,因为挡在 GitHub Enterprise 前面做鉴权的网关对任何请求都可能给出其中之一。所以 JSON 的 200 或 401 都只是候选,Codeg 会拿一条根本没有路由的路径去印证:真正的 GitLab 在那儿回 404,而笼统的网关还是照旧回答,于是 Codeg 拒绝下结论,而不是弄坏一套本来能用的部署。

被记住的是确定的答案,在本次进程内有效。主机不可达、或者服务器返回临时性错误,都留着这个问题不作结论,下次再问一遍 —— 一台恰好在重启的 Gitea,在 Codeg 真的听到它说话之前不会被判定为不支持。(Gitea 那一问得到临时性回答,并不会打断 GitLab 那一问:如果后者给出了确定的结论,那个结论算数。)

你在和哪一家 forge 对话,是由远端在 Codeg 这一侧推导出来的,客户端无权声称——因为这个选择决定了用哪份凭据。GitLab 的令牌绝不会被花在一次 GitHub 调用上,而一台使用自有域名的 GitHub Enterprise 主机,是按它的 URL 而不是按名字来解析的。

从 0.27.0 起 GitLab 有了自己的账户面板,Gitea 则从 0.30.4 起;GitLab 的令牌需要 api 权限范围,Gitea 的需要 write:repository、write:issue 和 read:user。→ 版本控制

找到那一条 ​

这个列表是一个分诊台,不是浏览器书签。顶部一排:

  • Issues 和 Pull requests——在 GitLab 上则是 Merge requests,因为 GitLab 用户根本没有 pull request,被告知自己有,读起来就像是走错了工具。每个标签页都会显示当前筛选状态下的数量,而你停在哪一个是跨会话记住的 —— 和每页条数并排,理由也一样:它说明的是你在哪个列表里干活。
  • 搜索,覆盖标题和描述。
  • 状态——Open、Closed 或 All。pull request 的行还会在适用时显示 Merged 和 Draft。
  • 分配给我——一个开关,面板里最快的筛选。
  • 标签——一个可搜索的多选框,取自仓库自己的标签。选的过程中它保持打开,因为选两个标签不该跑两趟;仓库一个标签都没有时,它整个都不出现。只列出前 100 个标签,遇到这个上限时它会说明。
  • 排序——最新、最早、最近更新、最久未更新。

分页在底部,带一个每页数量选择器(10 / 20 / 30 / 50,默认 20)。有两条限制值得知道,因为面板对这两点都是坦白的,而不是悄悄少给你一些:

  • 搜索超时时,它会说明这一页可能漏掉了一些匹配。
  • 只有前 n 条匹配可以翻页到达。 再往后 forge 自己的 API 就不再作答了,于是面板会让你收紧筛选条件,而不是给你一页根本不存在的内容。

点击某一行,它的详情会在侧边面板中打开——描述、每一个标签,以及同样的开始操作——这样读一个 issue 就不必赔上你的筛选条件、页码和滚动位置。(这个面板展示的是列表已经取回来的内容,而正文在取回时上限为 16,000 字符,所以真正巨大的 issue 还是值得去浏览器里打开。)想看真正的原帖时,在浏览器中打开就在那里。

从 0.28.2 起,这个面板还会带上讨论,而不只是描述——因为在多数 issue 上,描述只是开场,评论才是真正把事情谈定的地方。每条评论都通过与正文相同的 Markdown 渲染器呈现,带上作者、相对时间、只有在 forge 确实记录过编辑时才出现的已编辑标记,以及一个指向它的固定链接。加载更多继续往下翻。

这些讨论是在你打开面板时才去取的,不随列表一起取:把它折进列表,意味着为每一行都花掉一次请求,去画一个读者最多只会打开其中一条的页面。在 GitLab 上,那些事务性事件——更改了里程碑、指派给——会被过滤掉,所以你读到的就是对话本身,并与行上显示的评论数一致。上百条评论的讨论也能很快打开,因为只画屏幕上那部分,其余的随你滚动再到。

如果某一行的工作项已经有了任务,它显示的就不是开始——而是一枚状态标签,点击带你去看板,旁边还有一个小小的重新触发链接,用于确实该再跑一次的情形。

改动会分成三个标签页打开 ​

issue 仍是一条通栏滚动,因为只有一个标签的标签栏什么也没说明。而 pull / merge request 是三个,分支对留在头部,从哪个标签页都读得到:

  • Conversation —— 描述和讨论,和 issue 一样。
  • Checks —— CI,并把最差的状态作为标记放在标签上,于是构建红了不必点进去也看得见。三种答案被分开而不是糊在一起:绿的、没有配置任何检查,以及这个账户没有权限查看 —— 在 GitHub 上还有第四种「只读到一半」,因为 check runs 和 commit statuses 分属两种权限。
  • Files changed —— 文件列表,每一行都能打开它自己的 diff;当 forge 给出一个确切数字时,数量会放在标签上(改动特别大时 GitLab 报的是被截断的 1000+,而不是数字的计数不会显示)。

打开某个文件的 diff 不额外花一次请求:两家 forge 本来就随文件页一起送来了 hunk,只是 Codeg 从前把它们丢掉了。没有东西可打开的行不可点击。在 GitHub 上它会说明是两种原因中的哪一种 —— 二进制内容,或者因为体积太大被扣下、却仍然统计了行数的 diff。而在 GitLab 上,空的 diff 一律被报成二进制,所以那里请把它读作没有可用的 diff,而不是关于这个文件本身的判断。

不用离开就能动手 ​

在 0.29.0 之前,这个面板只会读。现在它也写:

  • 评论。 讨论下方的一个输入框,以读取该面板的那个账户的身份发出。
  • 关闭或重新打开。 一个按钮,并且会先确认 —— 与输入框不同,它在你什么都没输入的情况下,一次点击就写进了别人的仓库。
  • 新建 issue。 标题和正文。它会立刻打开,并且在你眼前这个列表容得下它的时候不必手动刷新就落进去 —— 也就是 Issues 标签页、第一页、按最新或最近更新排序、没有搜索词、不在指派给我、不是仅看已关闭,并且带齐你筛选的每一个标签。其余情形下它只在详情面板里打开,列表保持原样,而不是在你所处的这一页上塞进一行本不该在那儿的东西。

两种写入都以 forge 的回答为准,而不是假定自己那一下成功了。发出的评论会带着讨论用于去重的 id、你的令牌实际解析出的作者,以及它的固定链接一起回来。状态变更回来的是 forge 此刻提供的那一行 —— 于是一分钟前别人在浏览器里合并掉的 pull request,落下来是 merged,而不是你请求的那个 closed。

直接在这里合并 ​

打开着的改动不用浏览器就能合并。合并框位于讨论与输入框之间,把你需要拿主意的东西摆出来:两个分支会不会冲突,以及 CI 怎么说 —— 后者只在确实有结论可报时才出现,所以一个没有配置检查的仓库、或者这个账户读不到检查的情形,那里干脆什么都不显示,而不是给你一个让人安心的绿色。

按钮是一个拆分按钮,它的菜单就是这个仓库自己的设置 —— merge commit、squash、rebase,只在该仓库允许的地方才出现。一个只开了 squash 的仓库只会给你 squash,而不是给一个提交时必然失败的选项。如果这些设置读不到,Codeg 会退回到提供一个普通的 merge,而 forge 仍然可能拒绝它。

这和合并一个任务不是一回事

你在待办上按的合并,是把智能体的分支落进你自己的检出,并且可能需要一个智能体来解决冲突。而这里是在 forge 上合并一个 pull request,就像网页界面那样。不同的按钮、不同的地方、不同的含义 —— 来自 pull request 的任务依然完全不能在本地合并。→ 不会松动的规则

把它交出去 ​

开始会打开一个对话框,它的第一个问题正是最要紧的那个:这件事该怎么处理? 四种场景,每种工作项两种——你会看到哪一对,取决于你点的是什么。

对于 issue:

场景智能体被告知什么
直接修复 / 实现先确认问题确实存在,然后修掉它定位到的根因——并按这个项目自己的方式验证,构建、测试或 lint
先出方案先确认问题确实存在,然后交付一份实现方案——思路、要动的文件、风险、如何验证——然后停下。不改动任何文件

对于 pull / merge request:

场景智能体被告知什么
评审并修复先评判这次改动的方案是否站得住。站得住,就对着基线分支审查它,并就地修好值得修的地方;站不住,就什么都不提交,把评审交还给你
仅评审给出带位置、严重程度和修复建议的问题清单。不提交任何东西

有三件事,让这些和你自己写的提示词不一样。

两种 issue 场景都必须先确认问题,再动手。 这不是开场白,而是一个有结果的步骤。智能体被要求复现所报告的行为,或者从代码里指出它在哪里、为什么出错,并给出文件和行号;如果是功能请求,则要确认这个行为是真的缺失,而不是已经以另一个名字或另一个设置存在。在 0.28.2 之前,这曾是一个单独的仅调查场景,它被取消,恰恰是因为把它做成一个可选模式,会让「验证」看起来像另外两种流程可以跳过的事。→ 如果它其实并不成立

审查类场景拿到的是已经检出好的改动。 worktree 起始于 pull request 的 head 提交,所以智能体读的、构建的是那份真正的提案,而不是基线分支,它的提交也落在提案之上——这正是后面「推回去」这件事说得通的原因。

审查类场景被要求评判方案本身,而不只是 diff。 这个改动是否有必要;考虑到这份代码库的其余部分,这是不是最好的解法;就目前这个样子,它能上生产吗? 而且说得很明白:如果错的是设计本身,就把这一点说出来并提出更好的方案——不要把这个 pull request 改写成那样,因为一次作者从未要求过的重写不叫审查。

对「评审并修复」而言,这次评判是一道关卡,自 0.32.3 起如此。智能体必须先判断方案是否站得住——这个改动到底该不该做,它需要的是不是换一种设计而不是修修补补——当方案站不住,或者智能体自己拿不准时,它什么都不提交,连小修小补也不提交。它会说明原因并附上文件和行号,指出应该怎么做,再列出它发现的其他问题,并以这个结论开头。这样收尾算作成功,所以这条待办会像其他任务一样回到等你处理、交给你验收,而不是带着一堆修补、压在一个终归要推倒重来的设计上。评审进行到一半才发现的设计问题同样会叫停修复,报告里会写明哪些修复已经提交了。

对话框的其余部分:

  • 一条可选的额外指令,只针对这一条工作项。
  • 完成后回写评论——见下文。
  • 一份预览,看到这个任务将会携带的 issue 内容,也就看清了你即将送出去的是什么。
  • 一道重复检查。 如果已经有一个活跃任务在处理这条工作项,页脚会变成查看已有任务 / 仍然创建,而不是悄悄再造一个。重启一个所在工作项已有活跃任务的任务,也会以同样方式先提醒你。

创建之后,你得到的就是那个文件夹里一个普通的待办任务,它和其他任务一样,在该文件夹自己的并发上限之下开始。

如果它其实并不成立 ​

有时候,「确认问题」的答案是否。一个 issue 可能早已被修好、本就是设计如此、描述的是你并不在用的版本,或者干脆没提供足够的信息。

这会让任务以成功收场。 智能体被要求什么都不改,并报告它跑了什么、实际看到的是什么、最可能的解释,以及要想再往前一步还需要什么。这份结果和其他任务一样落到待审阅,而「复现不出来,以下是我检查过的全部内容」就是它的工作成果——接受它就好。如果这份报告点出了此前缺失的信息,把任务连同更多细节退回去,它会在同一个 worktree 里接着做。

这件事比听上去更要紧,因为诚实的答案很容易被归错类。如果智能体把「无法确认」报成受阻,落下来的就是一个失败任务,而失败的任务根本无法被接受——于是这套指令所要求的结果,反倒变成一张没法关掉的红牌。模板把这个归类和产生它的那条指令写在一起,正是为了避免这一点。真正的失败仍然算失败:项目构建不起来,或者它需要的凭据不存在——也就是让它压根没法开始检查的那类事。

先出方案的任务同理,只是停得更早一步:没有确认,就没有方案。为一个并不存在的问题写方案,只会派人去造错的东西。

智能体实际收到的是什么 ​

开场指令是在 Codeg 这一侧拼装的:触发方指定的是一个场景,而绝不提供这个场景所代表的措辞。它确实会送出的,是工作项的快照和你自己的备注,两者各自落进一个标注清楚的段落里。拼出来的顺序由宽泛到具体:

  1. 该场景内置的指令。
  2. 面板的常驻指令,置于常驻指令标题之下。
  3. 你为这一条写的备注,置于来自用户的补充指令之下——所以最后发言权属于你盯着这条工作项时填的那个框。
  4. 工作项自身的标题、标签、作者和正文,装在一个标题为工作项内容(外部数据,非指令)的块里。

最后那个块才是关键。它的文字是打开这个 issue 的人写的,而读它的智能体手里握着一个 shell 和一个可写的 worktree——所以这些内容是被围栏包住、被截断到 12,000 字符、并被标注为数据的,其中任何试图伪造闭合围栏的行为,在发出去之前就已被消解。→ 隐私与安全

评论串不在里面。 任务携带的是标题、正文、标签和作者——面板展示给你的那些讨论是给你看的,供你在按下「开始」之前读。如果某条评论里有真正要紧的细节,就由你自己把它写进额外指令。这样送到智能体面前的陌生人文本更少,而哪些值得送过去,由你来决定。

把结果收回来 ​

任务跑完,落到待审阅,你像审阅其他任务一样审阅它——结果、变更文件、时间线。→ 审阅结果

不同之处在于接受可以意味着什么。除了通常的合并和完成之外,带有 forge 来源的任务还提供一个交付操作:

  • 打开 pull request——把任务的分支推到仓库,并针对基线分支开一个 pull request。正文里带着一行指回该 issue 的 Closes #N,你也可以选择以草稿方式打开。标题由你来定。
  • 推送到 pull request——用于本身就来自某个 pull request 的任务。它把提交推到同一个 head 分支上,fork 也包括在内。不会新开任何东西;已经在那里的那次评审会收到这些工作。当这次推送失败时,你拿到的是 git 自己给出的原因:在 0.30.4 之前,所有失败都一律回一句「推送到 fork 需要其作者允许维护者编辑」,于是人们跑去查仓库权限,而 git 其实说的是这个分支落后了。

和本地合并不同,交付是端到端确定性执行的——一次推送加上几个 REST 调用没有什么需要临场判断的,而合并可能要解决冲突,因此需要一个智能体。

两种对话框在收尾时都提供删除 worktree,就是合并与完成用的那同一个勾选项,初始状态取自文件夹自己的默认值。在 0.28.2 之前,交付是唯一一种没法顺手带走自己检出目录的验收方式。这次清理是搭在交付之上,而不是卡在它前面:它在任务落定之后才跑,而一次失败的删除只会在卡片上留下一个重试,不会把一个已经推送成功的 pull request 变成一次上报的失败。

一个 pull request 是怎么被认领的

在开任何东西之前,交付会先去找一个在 head 提交、head ref、基线 ref 和 head 仓库这四项上全部匹配的 pull request。同一个提交完全可能针对不同基线开着好几个 pull request,所以只凭提交哈希会把任务结算到错误的那一个上。共有四种结果:

  • 匹配上且已合并——那就是这次交付。任务据此结算,而不是再开一个重复的。
  • 匹配上且处于打开状态——认领它,任务据此结算。
  • 匹配上但未合并就被关闭——任务退回审阅。那是一个人做出的决定,引擎不会用重开或复制的方式去推翻它。
  • 已经有一个从本分支开往这个基线的 pull request,但它指向另一个提交——有别人推送过。同样退回审阅,因为新建是不可能的(forge 对同一组 head/base 只允许一个打开的 pull request),而认领又会把任务结算到它从未产出的工作上。

如果什么都没匹配上,交付就会开一个新的 pull request——这也是一个任务首次交付时的常规情形。

分支是以触发该任务的那个账户推送的——而不是「此刻恰好是默认的那个账户」——所以之后把另一个账户设为默认,并不会悄悄改变由谁来执行这次推送。(提交的作者是另一回事,由智能体提交时所用的 git 配置决定。)

回帖的那条评论 ​

保持完成后回写评论开启,任务结算时 Codeg 就会在这条工作项上发出一条评论。它会说明是哪个任务、发生了什么,以及 diff 的计数:

  • 在本地合并进某个分支,附短提交号——这个措辞是刻意的,因为它落在你自己的检出里,从未被推送过。对于读这个帖子的其他人来说,那个分支毫无变化,那个哈希也指向不存在的东西。
  • 已交付,附 pull request 的 URL。
  • 已完成但没有落地任何东西。在 0.32.3 之前,这一条写的是 accepted,放在 pull request 上读起来就像是批准了它;现在它会写明发生了什么——在 pull / merge request 上是 it made no changes, so nothing was pushed,worktree 早已不在时则是 its work was not pushed;在 issue 上是 it made no changes 或 its work was not merged。

智能体写的任何文字都绝不会进入那个帖子——不是结果摘要,不是提交信息,也不是结论备注。这条评论由任务 id、结果和计数拼出来,而且根本没有一个参数能夹带别的东西。读那个帖子的是别人;智能体对自己工作的评价,不该由他们来接收。

如果这条评论发送失败,时间线也会记下这件事,而不是就此丢掉。

面板设置 ​

设置对话框的作用范围和任务设置完全一致——一份全局配置,加上可选的按文件夹覆盖——而且覆盖是整体生效的:保存某个文件夹的设置,会让它彻底脱离全局配置,而不是逐字段合并。这条规则是刻意照搬的;这两个对话框只隔着一次点击,不该用两套算术。

  • issue 的默认场景和 pull / merge request 的默认场景——「开始」对话框打开时预选哪一种。它只决定起始位置;随后发出的请求总会明确写出自己的场景。
  • 默认回写评论——那个开关的初始状态,你仍然可以逐条更改。
  • 常驻指令——按场景各一份,外加一个所有场景的槽位,会附加到全部场景上。「用中文回复。完成前先跑一遍项目的测试。」

常驻指令落在场景内置措辞之后、你为单条工作项写的备注之前,所以不需要把内置内容再复述一遍。它们只随任务的开场指令同行——你希望每一轮都生效的文字,应该写进任务设置的分阶段提示词里。每一条上限 4,000 字符,超出时会报错拒绝,而不是悄悄截断:你敲进去的文字不该不声不响地消失。

不会松动的那几条规则 ​

  • 任何由仓库工作项创建的任务都不会自动合并。 这不是一个你可以打开的设置,而是一条规则。它的提示词里嵌着任意外部用户写的文字,让它在无人看管的情况下落地,就等于铺了一条从注入到主分支的管道。设置了自动落地已审阅任务的文件夹会整个跳过这些行。对于来自 issue 的任务,你自己点的合并照常可用——只是必须由人来点。
  • 来自 pull request 的任务根本不能在本地合并。 它的工作属于那个 pull request 自己的分支,作者和评审者正看着那里——把它落到你的基线分支上,等于背着他们把这些改动收编进去:藏在一条本地提交里,而那个 pull request 还开着、看上去尚未合并。因此合并被替换为推送到 pull request,而且这条拒绝写在后端而不只是按钮上,所以旧客户端或直接调用 API 也一样撞到它。
  • 身份在触发时就被钉死。 由哪个账户读取工作项、推送分支,是在你按下「创建」时决定的,之后改动默认账户也不会移动它。更新那个账户的令牌会保住这个身份——删掉再重新添加则不会。

值得知道 ​

  • 不做任何缓存。 这个面板每次都直接读 forge 的 REST API——本地没有你 issue 的副本,也没有任何后台定时拉取。刷新是一个按钮。
  • 按文件夹生效。 面板作用于你选中的项目文件夹,并且会拒绝仓库与该文件夹远端不符的工作项,同时把两者都点出来。
  • 这一行可以隐藏。 如果你不用 forge 集成,在侧边栏的导航项里把仓库面板关掉即可——快捷操作依然能到达它。
  • 自建实例可用。 GitHub Enterprise、自管 GitLab,以及一台 Gitea 或 Forgejo 实例,都按各自的服务器 URL 解析,包括非默认端口或纯 http:// 的情形,回到工作项的链接也是用同一个来源拼出来的。「不从主机名去猜,而是直接问那台实例」这件事,对 GitLab 是从 0.30.0 开始的 —— 从前那种猜错正是以一个光秃秃的 410 收场 —— 而针对 Gitea 与 Forgejo 的那一问,是 0.30.4 才加上的。
  • 你配置过的主机,无论叫什么都会去试。 不带提供商的账户天生是有歧义的 —— 纯 git 凭据对话框写下的字节,和一个老式的 GitHub Enterprise 账户一模一样 —— 所以 Codeg 会去试,而不是为了让消息好看一点,就把一个本来能用的面板拿掉。

下一步 ​

  • 待办任务——每一条工作项最终落到的那块看板,以及任务如何运行、审阅和合并的全部内容。
  • 版本控制——这个面板读取所依赖的 GitHub、GitLab 和 Gitea 账户。
  • 隐私与安全——一个素未谋面的人写下的文字,会被怎样对待。
  • Git 与 Worktree——每个任务拿到的那个 worktree,以及用完之后如何清理。

基于 Apache-2.0 许可证发布。