Skip to content

Streamline session+worktree creation: quick-create and inline branch input#205

Merged
PureWeen merged 33 commits intomainfrom
cleaupworkflow
Feb 26, 2026
Merged

Streamline session+worktree creation: quick-create and inline branch input#205
PureWeen merged 33 commits intomainfrom
cleaupworkflow

Conversation

@PureWeen
Copy link
Owner

Summary

Streamlines the session + worktree creation flow, reducing it from a 7-step process to 1-2 clicks.

Changes

Foundation: Atomic \CreateSessionWithWorktreeAsync()\ API

  • New method in \CopilotService\ that combines worktree creation, session creation, linking, group organization, and optional initial prompt into a single atomic call
  • Includes rollback: if session creation fails after worktree was created, the worktree is cleaned up
  • Auto-generates branch names (\session-YYYYMMDD-HHmmss) when none specified

*Foundation: \WorktreeId\ on \AgentSessionInfo*

  • Added first-class \WorktreeId\ property to \AgentSessionInfo\ (previously only tracked via path string in \SessionMeta)
  • Enables future features like session restore, branch badge display, and worktree reattachment

UX: ⚡ Quick Session button

  • One-click button in repo group context menu (…)
  • Auto-generates branch name, creates worktree + session, switches to it
  • Zero form interaction needed

UX: ⑂ New Branch + Session inline input

  • Opens compact input bar below the group header
  • Type a branch name (or [feat] Add autopilot input mode #123\ for a PR) → press Enter → done
  • Collapses the previous 7-step flow to: click → type → Enter

Before vs After

Flow Before After
New session from repo 7 steps (expand form → open worktree picker → choose mode → enter branch → wait → fill name/model → create) 1 click (⚡ Quick Session) or 3 keystrokes (⑂ → type → Enter)
Session-worktree link Fragile path-string coupling First-class \WorktreeId\ on session
Failure handling Orphaned worktrees on partial failure Automatic rollback

Testing

  • Build succeeds ✅
  • 1256/1261 tests pass (5 pre-existing failures unrelated to this PR)
  • Looking for manual testing feedback on the new UX flows

cc @PureWeen

@PureWeen PureWeen force-pushed the cleaupworkflow branch 3 times, most recently from 0c12d88 to 547e7d8 Compare February 25, 2026 22:20
@PureWeen PureWeen marked this pull request as ready for review February 25, 2026 23:12
PureWeen and others added 26 commits February 25, 2026 17:39
…e branch input

- Add CreateSessionWithWorktreeAsync() to CopilotService: single atomic call that
  creates worktree, creates session, links them, organizes into repo group, and
  optionally sends initial prompt. Includes rollback on session creation failure.

- Add WorktreeId property to AgentSessionInfo for first-class session-to-worktree
  relationship (previously only tracked via path string and SessionMeta).

- Add Quick Session button (lightning bolt) to repo group context menu: one-click to
  auto-generate branch, create worktree+session, and switch to it.

- Add New Branch + Session button to repo group context menu: opens inline input
  below group header where user types branch name (or #PR number) and presses Enter
  to atomically create worktree+session. Replaces the 7-step form flow.

- Refactor HandleCreateSession to use atomic API when worktreeId is provided,
  eliminating scattered linking/organizing logic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clarifies that both options create a branch — the difference is whether
you name it yourself or get an auto-generated name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously opacity:0 and only visible on hover — easy to miss.
Now shows at 0.7 opacity on repo groups (0.5 on regular groups),
brightens to full on hover. Uses text-secondary color instead of
control-border for better contrast.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a session resumes with IsProcessing=true (mid-turn when app died),
ProcessingPhase was left at default 0 ('Sending') even though the session
was actively running tools. The phase only updated when a NEW event arrived
from the SDK, leaving a potentially long window of incorrect status display.

Now sets ProcessingPhase based on the last event in events.jsonl:
- If last activity was a tool call → phase 3 (Working)
- Otherwise → phase 2 (Thinking)
Also sets ProcessingStartedAt so elapsed time displays correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add MoveSessionToWorktree() to CopilotService: updates WorkingDirectory,
  WorktreeId, git branch, worktree link, and repo group membership in one call
- Add submenu in SessionListItem showing all available worktrees (grouped by
  repo name / branch) with the current worktree excluded
- Wire OnMoveToWorktree callback through SessionSidebar

Users can now reassign any session to a different worktree via the session's
... menu without recreating the session.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the previous 'list existing worktrees' submenu with a single
'Move to New Worktree...' menu item that opens an inline input below the
session. User types a branch name (or #PR) and the session is moved to
the new worktree while preserving the Copilot session and conversation
history.

- MoveSessionToNewWorktreeAsync() creates a new worktree, updates the
  session's WorkingDirectory/WorktreeId/GitBranch/meta/group, then sends
  a message to the agent notifying it of the directory change.
- Inline input supports repo selector when multiple repos exist.
- Session history and SDK session are fully preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The SDK doesn't support changing a session's working directory at runtime,
so moving a session to a different worktree can't reliably redirect the
agent's file operations. Removed entirely rather than shipping a half-measure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds WorktreeStrategy enum with three options:
- Shared: all sessions use one worktree (existing default)
- OrchestratorIsolated: orchestrator gets its own, workers share a separate one
- FullyIsolated: every session gets its own worktree

Implementation:
- WorktreeStrategy enum added to SessionOrganization.cs
- SessionGroup.WorktreeStrategy persists the chosen strategy
- GroupPreset.DefaultWorktreeStrategy sets per-preset defaults
- PR Review Squad defaults to FullyIsolated (workers checkout different PRs)
- CreateGroupFromPresetAsync auto-creates worktrees based on strategy:
  - FullyIsolated: creates N worker worktrees in parallel via Task.WhenAll
  - OrchestratorIsolated: creates 1 orchestrator + 1 shared worker worktree
  - Shared: no change from current behavior
- Orchestrator planning prompt now includes worktree paths for each worker
- Worker prompts include worktree awareness note when isolated
- DeleteGroup now cleans up all per-session worktrees (previously leaked)

All 113 multi-agent tests + 19 organization tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Since worktrees are now auto-created based on WorktreeStrategy, the
'Select worktree for team' step is replaced with 'Select repository
for team'. Users pick a repo, then a preset — worktrees are created
automatically (FullyIsolated creates one per agent, etc).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes:
1. Added a dropdown to select worktree strategy (FullyIsolated /
   OrchestratorIsolated / Shared) when creating multi-agent groups.
   Defaults to FullyIsolated.

2. Fixed session creation failure: parallel git worktree creation
   caused lock contention on bare repos. Now does one fetch upfront
   then creates worktrees sequentially with skipFetch flag.

Also fixed two callers of CreateWorktreeAsync that broke when the
skipFetch parameter was added (positional CancellationToken arg).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wrap all worktree creation calls in try-catch so that if git worktree
add fails (e.g., lock contention, disk issues, large repos), session
creation still proceeds with the fallback shared working directory.

Previously, a single worktree failure would abort the entire
CreateGroupFromPresetAsync, leaving an empty group with no sessions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Added a bordered card with header '🌿 Worktree Isolation' around the
strategy dropdown so it's clearly visible between the team name input
and the preset list. Previously it was a subtle inline element that
was easy to miss.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New WorktreeStrategyTests.cs covers:
- FullyIsolated: unique worktree per session, skip-fetch optimization
- OrchestratorIsolated: 2 worktrees, workers share one
- Shared: no worktrees created
- StrategyOverride: UI override takes precedence over preset default
- Error resilience: sessions still created when worktrees fail
- Session correctness: right count, orchestrator pinned, group wt ID

Also:
- Made RepoManager.CreateWorktreeAsync/FetchAsync virtual for testing
- Added diagnostic logs showing worktree strategy, directories, and
  per-worker worktree assignments for debugging

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…reation)

Root cause: 'PR Review Squad' has spaces, which are invalid in git
branch names. CreateWorktreeAsync silently failed for all workers
because 'git worktree add -b PR Review Squad-worker-1-xxxx' was
rejected by git. Only the orchestrator appeared to succeed (fell
back to an existing branch).

Fix: sanitize team name by replacing non-alphanumeric chars with
dashes before using it in branch names. 'PR Review Squad' becomes
'PR-Review-Squad'.

Added 2 tests for branch name sanitization (spaces and special chars).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
RemoveWorktreeAsync now wraps Directory.Delete and git worktree prune
in try-catch so a failure in either doesn't prevent state cleanup.
Also handles the case where the repo is gone but the directory exists.

Previously, if git worktree remove failed AND prune threw, the worktree
entry was never removed from state and the directory leaked.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The repo group context menu showed 'Remove Repo' even for multi-agent
teams created on that repo. This is confusing and dangerous — removing
the repo would orphan the team. Now hidden when group.IsMultiAgent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When git worktree add fails mid-operation, it can leave an empty or
partial directory behind. This directory isn't tracked by git's
worktree list, so RemoveWorktreeAsync can't clean it up later.

Now CreateWorktreeAsync wraps the git call in try-catch and deletes
the partial directory before re-throwing, preventing directory leaks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
RemoveWorktreeAsync now deletes the worktree branch after removing the
directory. Previously, branches like PR-Review-Squad-worker-1-xxxx
accumulated in the bare repo (30+ stale branches found).

Also cleaned up 31 stale branches and pruned worktree references.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add CreatedWorktreeIds list to SessionGroup that records every worktree
created during squad setup. DeleteGroup now uses this authoritative list
for cleanup, preventing leaks when session creation fails after worktree
creation succeeds. Adds 4 tests covering tracking across all strategies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ee strategy

Replace the hidden nested worktree picker with a flat, repo-first design
matching the multi-agent squad creation flow:
- Mode chips: Repo | Directory | Empty (Repo default when repos exist)
- Worktree segment: Auto | Branch | PR | Existing
- Auto mode generates branch name from session name (zero-config)
- Auto-naming: session name fills from branch/PR input
- Removed Options > Select worktree nesting (now all visible by default)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the two separate 'Close Session' and 'Close & Delete Worktree'
menu items with a single '🗑 Close Session…' item that shows an inline
confirmation panel. When the session has an associated worktree, a
checkbox appears to optionally delete the worktree and branch too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pass null working directory for Empty mode sessions instead of falling
back to ProjectDir. The SDK accepts null - the agent works without a
codebase context. Directory mode still falls back to ProjectDir when
the input is blank.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create a unique temp folder under polypilot-sessions/ for each scratch
session so the agent has a clean workspace without access to any
existing codebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace inline confirmation with centered modal dialog popup. Add
separate checkboxes for 'Delete worktree' and 'Delete branch', both
checked by default when session has a worktree. RemoveWorktreeAsync
now accepts deleteBranch parameter to support keeping the branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the modal overlay to the root level of the component (outside the
session-item div) so it renders above all sidebar content. Also fix
stray closing brace and bump z-index.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three-model code review identified these bugs:

1. deleteBranch=true default silently changed all existing callers to delete
   branches (Sonnet). Fixed: default to false, explicit true where intended.

2. RemoveWorktreeAsync deletes arbitrary dirs when repo lookup fails (Opus).
   Fixed: validate path is under WorktreesDir before recursive delete.

3. Empty branchPrefix from sanitization produces invalid git names like
   '-worker-1-xxxx' (Sonnet+Codex). Fixed: fallback to 'team'/'session'.

4. Delete branch checkbox is silently no-op without delete worktree (all 3).
   Fixed: disable checkbox + dim when worktree unchecked, auto-uncheck.

5. DeleteGroup fire-and-forget can abort cleanup loop on first failure
   (Codex). Fixed: per-item try/catch with debug logging.

6. Session name dedup collides within same minute (Opus). Fixed: use
   incrementing counter loop instead of HHmm timestamp.

7. Empty session temp dirs leak forever (Opus+Codex). Fixed: cleanup
   polypilot-sessions/ temp dirs on CloseSessionAsync (path-validated).

8. CreateSessionWithWorktreeAsync broken in remote mode (Opus). Fixed:
   throw NotSupportedException with clear message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen and others added 4 commits February 25, 2026 17:40
…tion)

CloseSessionIconTests: Updated to reflect intentional change from ✕ to 🗑
icon — SessionListItem's close now opens a dialog with destructive options
(delete worktree/branch), so the trash icon is appropriate.

InputSelectionTests: Updated CSS class references after CreateSessionForm
rewrite (wt-branch-input → ns-name, removed ns-input since the directory
input uses @Bind not @onkeyup).

Verified: remaining 7 DiffParser+ServerManager failures also fail on
origin/main (pre-existing Windows line-ending and flaky network issues).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…w:hidden

Root cause: The close dialog overlay rendered inside SessionListItem which
is nested in sidebar > sidebar-content > session-list, all with overflow:hidden.
Even though the overlay used position:fixed, the overflow clipping on ancestor
elements prevented it from being visible outside the 280px sidebar.

Fix: After the dialog renders, use JS interop to move (portal) the overlay
element to document.body, escaping the overflow:hidden containment chain.
Also improved dialog styling: darker overlay (0.7 opacity), brighter dialog
background (#2a2a3e), purple accent border with glow shadow, white title text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
QuickCreateSessionForRepo errors were swallowed silently because
createError is only displayed in CreateSessionForm (which isn't open
during quick-create). Added quickCreateError/quickCreateErrorRepoId
fields that render a dismissible error bar under the relevant group.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…load

If Load() throws (e.g. corrupted JSON), _state becomes empty but _loaded=true
prevents re-loading. Any subsequent Save() (from worktree operations etc.) would
overwrite repos.json with empty state, silently destroying all repo entries.

Added _loadedSuccessfully flag that Save() checks before writing empty state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen and others added 3 commits February 25, 2026 18:02
Critical:
- Fix DOM portal orphaned overlays: HideCloseConfirm and ConfirmClose
  now remove the portaled element from document.body before Blazor diffs
- Fix async void ShowCloseConfirm → async Task
- Remove dead needsPortal variable

Moderate:
- Remove dead deleteBranch-without-deleteWorktree code path
- Add PR number validation in CommitQuickBranch (reject #0, #abc)
- Fix null! suppression: use args.RepoId! (already null-checked)
- Set WorktreeId on AgentSessionInfo in CreateGroupFromPresetAsync
  (was only set on SessionMeta, inconsistent with CreateSessionWithWorktreeAsync)
- Fix race condition in DeleteGroup: add _stateLock to RepoManager for
  thread-safe List<T> access (Repositories/Worktrees getters return copies)
- Fix shared strategy creating temp dirs: auto-create shared worktree when
  repo is set but no workingDirectory/worktreeId provided
- Fix _loadedSuccessfully gap: set flag true on first successful Save()
  so subsequent removes persist correctly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eeId

New tests:
- Save_AfterFailedLoad_DoesNotOverwriteWithEmptyState: verifies the
  _loadedSuccessfully guard prevents wiping repos.json after a parse error
- Save_AfterSuccessfulLoad_PersistsEmptyState: normal remove-all-repos works
- Repositories_ReturnsCopy_ThreadSafe: verifies .ToList() snapshot isolation
- Shared_WithRepoButNoWorkDir_CreatesSharedWorktree: verifies auto-create
  when no workingDirectory is provided (was creating temp dirs before fix)
- Shared_WithExistingWorkDir_CreatesNoWorktree: existing behavior preserved
- FullyIsolated_WorktreeIdSetOnAgentSessionInfo: verifies WorktreeId is set
  on AgentSessionInfo (not just SessionMeta)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Critical:
- IAsyncDisposable on SessionListItem: removes portaled overlay on dispose
  to prevent permanent UI-blocking dark backdrop
- Scoped data-dialog-id selector: each instance uses a unique ID instead of
  global .close-dialog-overlay, preventing cross-instance interference

Moderate:
- Shared strategy workers use orchWorkDir: fallback chain is now
  workerWorkDirs[i] ?? orchWorkDir ?? workingDirectory (was skipping orchWorkDir)
- Consistent _stateLock on all Add/Remove paths in RepoManager
- Git -- separator added to worktree add and branch -D commands
- args.RepoId derived from worktree when only WorktreeId is set
- Quick-branch input sanitized with Regex (matching CreateSessionForm)

Tests:
- Shared_WithRepoButNoWorkDir now verifies all sessions share same directory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant