feat(workspace): a session in its own worktree, Claude's way
Desktop-parity phase 6, following Claude Desktop: - the workspace home's Worktree checkbox starts the pane with `claude --worktree=<name>`, so Claude Code makes, populates (.worktreeinclude, WorktreeCreate hook) and cleans up <repo>/.claude/worktrees/<name>; AgentHub only excludes /.claude/worktrees/ at the repo root (Claude does not) - server/lib/session-worktree.js resolves a session's worktree from the session row only (owned by the workspace, registered by git worktree list); every lane git route takes ?session= and answers ENOWORKTREE instead of falling back to the main folder - the session view's Changes panel works in the opened run's worktree (dashboard_runs.worktree, GitTarget in the client api) - Resume/Fork start in the session's worktree; a gone one is ENOWORKTREE - Archive of a worktree session removes the worktree after asking: git worktree remove without --force, git branch -d (unmerged work keeps its branch), a lock held by a live pid refused, a stale one cleared - listWorktrees reads `locked <reason>` locks - tests scrub GIT_* env so the pre-commit suite cannot act on this repo
This commit is contained in:
+28
@@ -272,6 +272,7 @@ All fields are optional. The tool (CLI invoked) comes from the workspace itself,
|
||||
- **`approval`** — one of `on-request` or `never`, or omitted/empty for no approval prompt. Passed to `codex -a <value>`.
|
||||
- **`resumeSessionId`** or **`resumeLast: true`** — resume an earlier run instead of starting new. For Codex workspaces specifically, `resumeLast: true` uses `codex resume --last`; `resumeSessionId` uses `codex resume <sessionId>`. For Claude workspaces, `resumeSessionId` re-attaches a Claude session by id.
|
||||
- **`forkSession: true`** (with `resumeSessionId`, Claude workspaces only) — resume into a NEW session id (`claude --resume <id> --fork-session`), leaving the original untouched; the run records no session until the fork's first hook. Without `resumeSessionId` it is `400 EBADSESSION`, on any other tool `400 EUNSUPPORTED`.
|
||||
- **`worktree: true`** (Claude workspaces, no `resumeSessionId`) — a NEW session in a git worktree of its own, Claude Code's way: the pane runs `claude --worktree=ah-<id>-<base36 time>` (the `=` form, since `-w [name]` would take the prompt as the name; the name is made by the server), so Claude itself creates `<repo>/.claude/worktrees/<name>` on branch `worktree-<name>`, copies `.worktreeinclude`, runs the user's `WorktreeCreate` hook and cleans up on exit. `/.claude/worktrees/` is added to the repo root's `info/exclude`; the run is recorded with `worktree: 1` (and the run handle carries `worktree: true`). `400 EUNSUPPORTED` on any other tool. A `resumeSessionId` (or fork) of a session that ran in such a worktree starts the pane IN that worktree; one whose worktree is gone is `400 ENOWORKTREE`.
|
||||
|
||||
- **`slot`** — which Workspace split pane's session to start, `0`–`3` (default `0`, else `400 EBADSLOT`). Slot 0 is the workspace's own `agenthub-lane-<id>` and is what `run_id` records; slots 1–3 are extra sessions on the same workspace, `agenthub-lane-<id>-<slot>`, and never touch `run_id`/`status`. The response adds `run_id`: the session this start opened. `stop`, `reset` and `remove` kill every slot's session, not only `run_id`.
|
||||
|
||||
@@ -576,6 +577,33 @@ comes from the workspace row, never from the query (that is what `GET
|
||||
/api/lanes/branches` is for, which takes a candidate path for a workspace that does
|
||||
not exist yet).
|
||||
|
||||
#### A session's own worktree (`?session=`)
|
||||
|
||||
Every `/api/lanes/:id/git…` route above and below takes `?session=<id>`: the
|
||||
read or write then works in that session's worktree instead of the
|
||||
workspace's folder. The tree is resolved from the SESSION ROW
|
||||
(`server/lib/session-worktree.js`): the session must be owned by this
|
||||
workspace (`sessions.lane_id`), its `cwd` must not be the workspace's own
|
||||
folder, and `git worktree list` of the workspace's repo must register it —
|
||||
otherwise `400 ENOWORKTREE`, never a fall back to the main folder. With it,
|
||||
`GET /git` also answers `worktree: {path, branch}`.
|
||||
|
||||
#### Remove a session's worktree
|
||||
|
||||
```http
|
||||
POST /api/lanes/:id/session-worktree/remove
|
||||
```
|
||||
|
||||
Body `{"session": "<id>", "confirm": true}` (same-origin). Claude Desktop's
|
||||
archive of a worktree session: `git worktree remove <path>` WITHOUT force
|
||||
(git refuses modified or untracked files: `409 EGIT` with git's message), then
|
||||
`git branch -d` of its branch (`-d`, never `-D`: a branch with commits merged
|
||||
nowhere is kept, `branchKept: true`). A lock held by a live process is `409
|
||||
ELOCKED`; a stale one (the pid in Claude's `claude session <name> (pid …)`
|
||||
reason is gone) is cleared first. A live pane still holding the session is
|
||||
`409 ELIVE`; no `confirm` is `400 ECONFIRM`; a session with no worktree here
|
||||
`400 ENOWORKTREE`. Answers `{removed: true, branch, branchKept}`.
|
||||
|
||||
#### Act on a workspace's working copy
|
||||
|
||||
```http
|
||||
|
||||
Reference in New Issue
Block a user