文件夹 Git 胶囊#
会话用到的每个工作目录,只要位于 Git 仓库里,它在输入框环境条里的文件夹胶囊就多出一个 git 半边。git 半边回答"这个目录在哪个分支、改了什么",菜单里放聊天时需要的几个 Git 操作,不必离开会话:切分支、在 worktree 里工作、审阅修改、开 PR。
哪些目录有胶囊#
- 主项目目录:会话绑定了真实项目时。默认的主目录项目没有胶囊——它的仓库是 OpenProgram 自己的存储,不是用户的代码。
- 每个额外工作目录。
- 不在 Git 仓库里的目录什么都不显示。
同一个仓库里的两个目录(比如项目根目录和它的一个子目录)分支、修改、PR 都相同,只显示一个 git 半边。每个 git 半边按顺序认领自己的仓库根目录——项目在前,工作目录按列表顺序——顺序最小的显示(components/chat/top-bar/git-chip-registry.ts)。同一仓库的不同 worktree 根目录不同,各自保留 git 半边。
胶囊#
每个文件夹一个凸起胶囊(components/chat/top-bar/folder-pill.ts),中间一条贯通上下的细线分成两段,各自响应 hover 和点击。细线不占宽度,两段的 hover 底色在同一条边界相接;任一段 hover 或展开时细线淡出。左段是文件夹:图标、名字;工作目录的 ✕ 只在 hover 或键盘聚焦时出现。点击打开项目菜单。右段是 git:Solar git-branch,分支名的最后一段(claude/foo 显示 foo;HEAD 游离时显示短提交号),再接未提交的 +N −N diff badge——绿半边、红半边,与文件修改卡片一致。未跟踪的新文本文件按新增行计入。改动全是二进制文件(没有行数)时,badge 显示中性灰色的“N 个文件”,第 3 级的圆点也是灰色,不显示“+0 −0”。干净的目录只显示分支。两段都撑满胶囊高度,hover 底色覆盖整个半边。tooltip 里有仓库、完整分支名和计数。
整行放不下时逐级收缩:
| 级别 | 显示 |
|---|---|
| 0 | 全部 |
| 1 | 文件夹名和分支名截到约 12 个字加省略号;Local chip 只留图标 |
| 2 | 收起分支名:分支图标 + diff badge |
| 3 | 隐藏全部名字;diff badge 变成左绿右红的小圆点 |
测量规则见 composer-responsive-controls.html。
菜单#
- 标题:仓库名;当前目录是附属 worktree 时带
worktree标签。 - 修改:"N 个未提交文件 +N −N"。点击以 workspace 范围打开 Review,它本来就按 Git 对比每个工作目录。发送第一条消息前没有会话可审阅,这一行不响应。与上游有领先 / 落后时接着显示一行。
- 分支:筛选框 + 本地分支,按最近提交排序(最多五十个)。点分支去哪里由目录状态决定,没有开关可选:目录干净就地执行
git switch;目录有未提交修改时,分支在仓库旁边的新 worktree 里打开,并把这个文件夹位置换过去,原目录的文件保持不动,标题行右侧写一句"在新 worktree 里打开"。行尾标签只在有信息量时出现:当前所在分支标当前,已在其他 worktree 检出的分支标worktree,点它改为去那个目录(git 规定一个分支只能在一处检出)。输入不存在的名字会出现"新建分支"(或"在新 worktree 上新建分支"),回车同效。文件夹位置无法换到 worktree 时始终就地切换。 - Worktree:新 worktree 建在仓库旁边的
<repo>-worktrees/<branch>,从不建在仓库里面,原目录的文件和未提交修改保持不动。没有单独的 worktree 列表:有 worktree 的分支就在分支列表里带着标签。"换到 worktree"按目录位置分三种:- 工作目录:在会话的目录列表里替换;
- 未发送草稿的主目录:草稿的项目改指向该 worktree(登记为项目);
- 已开始会话的主目录:已固定,worktree 作为额外工作目录加入,菜单里有说明。
- Pull request:
- 分支已有打开的 PR:显示"查看 PR #N",在系统浏览器打开;
- 否则"创建 PR":推送分支(
git push -u origin HEAD),以默认分支为基执行gh pr create --fill,然后打开新 PR。没有gh、没有origin、HEAD 游离或当前就在默认分支时禁用,并在下方写明原因。分支相对基分支没有新提交时后端也会拒绝; - 有未提交修改时,"让 agent 提交并创建 PR…"把一段现成指令填进输入框(不发送)——写提交信息和 PR 描述是 agent 的活。
git 或 gh 的报错以卡片形式显示在菜单底部,菜单保持打开,方便重试。卡片只有一行人话标题,git 的原始输出放在"查看 git 输出"文字按钮后面,不用原生折叠控件。有一种拒绝会多给内容:就地切换会覆盖未提交修改时,卡片列出文件名,并给两条出路——改为开 worktree 和 带着修改切过去。后者把所有修改(含未跟踪文件)stash 起来,切换后再 pop;pop 冲突时切换保留,修改留在最新的 stash 里,卡片会说明。不提供强制切换,也不提供丢弃修改:agent 会话里不该丢工作。
刷新时机#
不轮询。胶囊在以下时机读取目录状态:挂载和目录变化时;菜单打开时(只有这次会同时列分支、查 PR,二者开销更大);所在会话一轮结束时;窗口重新获得焦点时;项目变化时;同一仓库上任何胶囊执行修改后(op:git-changed)。
后端#
逻辑在 openprogram/worktree/folder_git.py;ws_actions/folder_git.py 把它挪出事件循环并组装回复。每条回复回显 path,并发的多个胶囊各取各的。
| 动作 | 回复 | 做什么 |
|---|---|---|
git_folder_status |
git_folder_status |
status --porcelain=v2 --branch(分支、上游、领先 / 落后、文件数),diff --numstat HEAD(行数),worktree list,默认分支,是否有 origin,是否有 gh;带 include_branches / include_pr 时再列分支、执行 gh pr view |
git_switch_branch |
git_switch_branch_result |
git switch [-c] <branch>;带 carry 时 stash push -u → switch → stash pop。拒绝帧带 code(overwrite 附 files,或 stash_conflict)和 detail(git 原始输出) |
git_create_worktree |
git_worktree_created |
git worktree add [-b] <仓库旁的路径> <branch> |
git_create_pr |
git_pr_created |
推送分支,gh pr create --fill,返回 URL(或已存在的打开 PR) |
状态读取带 GIT_OPTIONAL_LOCKS=0,不与用户自己的 git 命令争锁;带 GIT_TERMINAL_PROMPT=0,需要凭据的推送直接失败而不是卡住。
实现状态#
- 项目目录和工作目录的胶囊、同仓库去重、切换 / 新建分支、worktree 新建 / 切换、修改计数与 Review、创建 / 查看 PR、交给 agent:已实现。
- PR 的基分支选择(目前固定为仓库默认分支)和草稿 PR:未实现。
- pull / push / fetch 按钮,以及没有上游时相对默认分支的领先 / 落后:未实现。