docs/design/2026-07-22-webshell-session-git-mode.md
日常开发中,用户新建会话时有三种 Git 工作流:
场景 1 和 2 已有完整支持(场景 2 见
2026-07-19-webshell-worktree-sessions.md
和
2026-07-20-worktree-empty-state-toggle.md)。
场景 3 缺失——用户想"开个新分支做这个任务"时,只能先手动 git checkout -b
再建会话,或者被迫使用 worktree(引入不必要的目录隔离)。
POST /session 时自动 git checkout -b,
session 直接在新分支上启动。enter_worktree / exit_worktree 工具行为。模式选择器不做成独立区块,而是内嵌到 composer 底部工具栏——复用
现有 git chip 的位置(输入框下方、发送按钮左侧)。chip 默认显示当前
分支 ⎇ main,点击弹出 popover 选择模式:
┌─ composer ───────────────────────────────────────────┐
│ 描述你的任务… │
│ │
│ 📎 @ 🎙 [⎇ main ▾] [发送] │
└──────────────────────────────────────────────────────┘
│ 点击
▼
┌─ Git 模式 popover ─────────────┐
│ ● 当前分支 直接在 main 上 │
│ ○ 新建分支 从 main 创建 │
│ [分支名输入框 — 选中时展开] │
│ ○ Worktree 独立副本,可并行 │
│ ───────────────────────────── │
│ $ git checkout -b feat/x ← main│
│ [创建分支] │
└─────────────────────────────────┘
⎇ main(绿色),等同于现有行为。
选中后 popover 自动关闭。⎇ → feat/xxx(橙色),带 ✕ 可一键恢复默认。⎇ worktree 隔离(紫色),带 ✕ 可一键恢复默认。popover 底部实时预览将执行的 git 命令(git checkout -b … /
git worktree add …),让用户明确知道会发生什么。
chip 方案的优势:不占用 welcome 区垂直空间;入口在用户注意力所在的 composer 内;非空状态(已有会话)下 chip 依然可见,语义一致。
可见性条件与现有 worktree toggle 一致:workspace 已信任 + 是 git 仓库。 不满足时 chip 退化为只读分支指示器(现有行为)。
将 pendingWorktreeRef / worktreePending 扩展为统一的 pending 意图:
type SessionGitIntent =
| { mode: 'current' }
| { mode: 'branch'; name: string }
| { mode: 'worktree'; slug?: string };
{ mode: 'current' }(等同于 undefined,不传参)。{ mode: 'branch', name }。{ mode: 'worktree', slug? }(复用现有逻辑)。ensureSessionForPrompt 根据 intent 携带对应参数。CreateSessionRequest(SDK)export interface CreateSessionRequest {
// ... existing fields ...
worktree?: { slug?: string };
/**
* Create a new git branch and check it out before starting the
* session. The session runs in the same working directory but on
* the new branch. Mutually exclusive with `worktree`.
*/
branch?: { name: string };
}
branch 与 worktree 互斥,同时传入返回 400。
DaemonSession / DaemonSessionSummary 响应export interface DaemonBranchInfo {
name: string; // 新建的分支名
baseBranch: string; // 创建时的基础分支
}
export interface DaemonSession {
// ... existing fields ...
worktree?: DaemonWorktreeInfo;
branch?: DaemonBranchInfo;
}
POST /session 路由处理(routes/session.ts)在现有 worktree 处理逻辑之前,增加 branch 处理:
1. 校验 branch / worktree 互斥
2. 校验 branch.name 是合法 git 分支名
3. 检查分支名不与现有分支冲突(git rev-parse --verify)
4. 检测 dirty tree(git status --porcelain),有改动则 409 branch_dirty_tree
5. 记录 baseBranch = 当前分支(git rev-parse --abbrev-ref HEAD)
6. git checkout -b <name>
7. branchMeta = { name, baseBranch }
8. 强制 sessionScope = 'thread'
9. 正常 spawnOrAttach(cwd 不变)
10. 失败回滚:git checkout <baseBranch> && git branch -D <name>
不需要 changeSessionCwd(工作目录不变),不需要 worktree marker。
| 错误码 | 含义 |
|---|---|
branch_and_worktree_conflict | 同时传了 branch 和 worktree |
invalid_branch | branch 字段不是对象(需为 {"name":"..."}) |
branch_invalid_name | 分支名不合法 |
branch_session_conflict | 该 workspace 已有分支 session,或共享 checkout 上已有其他活跃 session |
branch_init_failed | 初始化 git 服务失败 |
branch_not_git_repo | workspace 不是 git 仓库 |
branch_already_exists | 分支名已存在 |
branch_status_failed | 检查工作目录状态失败 |
branch_dirty_tree | 工作目录有未提交改动,需先 commit 或 stash |
branch_checkout_failed | git checkout -b 失败(其他原因) |
App.tsx (gitIntent state)
→ sessionPreparation.ts createAndAttachSessionForPrompt({ branch })
→ actions.ts createSession({ branch })
→ DaemonClient.createOrAttachSession({ branch })
→ POST /session { branch: { name } }
与 worktree 链路完全对称,每层增加 branch 透传。
GitForkIcon badge,不变。GitBranchIcon + 分支名 badge。同一 workspace 的"新建分支"session 会改变共享工作目录的 HEAD,多个 branch session 会互相冲突。限制策略:
POST /session 带 branch 时,检查同一 workspace 是否已有
活跃的 branch session(通过 bridge 的 session 列表 + branchMeta)。
如有,返回 409 branch_session_conflict。Worktree session 不受此限制(各自独立目录)。
| 文件 | 改动 |
|---|---|
packages/sdk-typescript/src/daemon/DaemonClient.ts | CreateSessionRequest 增加 branch 字段 |
packages/sdk-typescript/src/daemon/types.ts | DaemonBranchInfo、DaemonSession.branch、DaemonSessionSummary.branch |
packages/cli/src/serve/routes/session.ts | POST /session branch 创建逻辑 + 回滚 |
packages/webui/src/daemon/session/actions.ts | createSession 透传 branch |
packages/webui/src/daemon/session/types.ts | createSession 签名增加 branch |
packages/web-shell/client/App.tsx | SessionGitIntent 状态机、模式选择器 UI、并发检查 |
packages/web-shell/client/App.module.css | 选择器样式 |
packages/web-shell/client/utils/sessionPreparation.ts | 透传 branch |
packages/web-shell/client/i18n.tsx | 新增 i18n keys(en/zh) |
packages/web-shell/client/components/sidebar/WebShellSidebar.tsx | branch session badge |
| Key | EN | ZH |
|---|---|---|
gitMode.current | Current branch | 当前分支 |
gitMode.branch | New branch | 新建分支 |
gitMode.worktree | Worktree | Worktree 隔离 |
gitMode.branch.placeholder | Branch name | 分支名 |
gitMode.branch.hint | Switches the working directory to a new branch | 在工作目录中切换到新分支 |
gitMode.branch.conflictWarning | Only one branch session per workspace at a time | 同一 workspace 同时只能有一个分支会话 |
gitMode.branch.invalidName | Invalid branch name | 分支名不合法 |
gitMode.branch.exists | Branch already exists | 分支已存在 |
gitMode.branch.dirtyTree | Uncommitted changes detected. Commit or stash first. | 检测到未提交改动,请先 commit 或 stash |
feat/my-feature),减少预设。git checkout -b 前检测 dirty 状态
(git status --porcelain)。如有未提交改动,返回 409
branch_dirty_tree,前端提示用户先 commit 或 stash 后再创建分支会话。
不在 UI 层预检测(避免与 git 实际行为脱节),统一由服务端判定。git branch 即可知当前分支,无需额外记录。
注意:DaemonSessionSummary.branch 目前仅保存在内存中(bridge 映射),
daemon 重启后会丢失,因此 sidebar badge 与并发守卫不会跨重启保留;
持久化属于后续工作。