@@ -35,7 +35,7 @@ import * as GitWorkflowService from "../git/GitWorkflowService.ts";
3535import * as ProjectService from "../project/ProjectService.ts" ;
3636import * as ProviderAuthService from "../provider/ProviderAuthService.ts" ;
3737import * as ServerSettings from "../serverSettings.ts" ;
38- import { makeIsManagedWorktreePath } from "../worktreesDirectory .ts" ;
38+ import { makeRestoreMissingWorktree } from "../workspace/restoreMissingWorktree .ts" ;
3939import * as EventSink from "./EventSink.ts" ;
4040import * as ProviderUsageAnalytics from "../telemetry/ProviderUsageAnalytics.ts" ;
4141import * as ContextHandoffService from "./ContextHandoffService.ts" ;
@@ -77,28 +77,6 @@ export class ProviderTurnStartError extends Schema.TaggedError<ProviderTurnStart
7777 } ,
7878) { }
7979
80- class WorktreeRecreateError extends Schema . TaggedError < WorktreeRecreateError > ( ) (
81- "WorktreeRecreateError" ,
82- {
83- worktreePath : Schema . String ,
84- branch : Schema . String ,
85- cause : Schema . Defect ( ) ,
86- } ,
87- ) {
88- override get message ( ) : string {
89- return `Could not recreate the worktree for branch '${ this . branch } ' at ${ this . worktreePath } .` ;
90- }
91- }
92-
93- class WorktreeRecreateNoBaseError extends Schema . TaggedError < WorktreeRecreateNoBaseError > ( ) (
94- "WorktreeRecreateNoBaseError" ,
95- { worktreePath : Schema . String , branch : Schema . String } ,
96- ) {
97- override get message ( ) : string {
98- return `Could not recreate branch '${ this . branch } ' at ${ this . worktreePath } : the project workspace has no commit to start it from.` ;
99- }
100- }
101-
10280const isProviderTurnStartError = Schema . is ( ProviderTurnStartError ) ;
10381const isProviderAdapterProtocolError = Schema . is ( ProviderAdapter . ProviderAdapterProtocolError ) ;
10482const isProviderAdapterOpenSessionError = Schema . is (
@@ -176,80 +154,14 @@ export const layer: Layer.Layer<
176154 const turnAnalytics = yield * ProviderUsageAnalytics . ProviderTurnAnalytics ;
177155 const contextHandoffService = yield * ContextHandoffService . ContextHandoffServiceV2 ;
178156 const idAllocator = yield * IdAllocator . IdAllocatorV2 ;
179- const fileSystem = yield * FileSystem . FileSystem ;
180157 const path = yield * Path . Path ;
181- const gitWorkflow = yield * GitWorkflowService . GitWorkflowService ;
182- const projects = yield * ProjectService . ProjectService ;
183158 const providerAuth = yield * ProviderAuthService . ProviderAuthService ;
184159 const projectionStore = yield * ProjectionStore . ProjectionStoreV2 ;
185160 const providerSessions = yield * ProviderSessionManager . ProviderSessionManagerV2 ;
186161 const runExecution = yield * RunExecutionService . RunExecutionServiceV2 ;
187162 const runtimePolicy = yield * RuntimePolicy . RuntimePolicyV2 ;
163+ const restoreMissingWorktree = yield * makeRestoreMissingWorktree ;
188164 const turnItemPositions = yield * TurnItemPositionStore . TurnItemPositionStoreV2 ;
189- const isManagedWorktreePath = yield * makeIsManagedWorktreePath ;
190-
191- const resolveRecreateBase = Effect . fn ( "ProviderTurnStartService.resolveRecreateBase" ) (
192- function * ( cwd : string ) {
193- // Recovery can follow an external checkout, before the status cache expires.
194- yield * gitWorkflow . invalidateLocalStatus ( cwd ) ;
195- const { refName : currentBranch } = yield * gitWorkflow . localStatus ( { cwd } ) ;
196- if ( currentBranch !== null ) {
197- const refName = `refs/heads/${ currentBranch } ` ;
198- return { refName, baseRefName : refName } ;
199- }
200- if ( yield * gitWorkflow . hasCommit ( { cwd, refName : "refs/remotes/origin/HEAD" } ) ) {
201- const { commitSha } = yield * gitWorkflow . resolveRemoteTrackingCommit ( {
202- cwd,
203- refName : "HEAD" ,
204- fallbackRemoteName : "origin" ,
205- } ) ;
206- // Start from the commit so the thread branch does not track the default branch.
207- return { refName : commitSha , baseRefName : "refs/remotes/origin/HEAD" } ;
208- }
209- if ( yield * gitWorkflow . hasCommit ( { cwd, refName : "HEAD" } ) ) {
210- return { refName : "HEAD" , baseRefName : undefined } ;
211- }
212- return null ;
213- } ,
214- ) ;
215-
216- const recreateMissingWorktree = Effect . fn ( "ProviderTurnStartService.recreateMissingWorktree" ) (
217- function * ( input : {
218- readonly cwd : string ;
219- readonly worktreePath : string ;
220- readonly branch : string ;
221- } ) {
222- const { cwd, worktreePath, branch } = input ;
223- yield * gitWorkflow . pruneWorktrees ( { cwd } ) ;
224- const localBranchNames = yield * gitWorkflow . listLocalBranchNames ( cwd ) ;
225- if ( localBranchNames . includes ( branch ) ) {
226- yield * gitWorkflow . createWorktree ( { cwd, refName : branch , path : worktreePath } ) ;
227- return ;
228- }
229- // Push keeps the tracking ref current, so recovery reads it without fetching.
230- const pushedRef = `refs/remotes/origin/${ branch } ` ;
231- if ( yield * gitWorkflow . hasCommit ( { cwd, refName : pushedRef } ) ) {
232- yield * gitWorkflow . createWorktree ( {
233- cwd,
234- refName : pushedRef ,
235- newRefName : branch ,
236- path : worktreePath ,
237- } ) ;
238- return ;
239- }
240- const base = yield * resolveRecreateBase ( cwd ) ;
241- if ( base === null ) {
242- return yield * new WorktreeRecreateNoBaseError ( { worktreePath, branch } ) ;
243- }
244- yield * gitWorkflow . createWorktree ( {
245- cwd,
246- refName : base . refName ,
247- newRefName : branch ,
248- ...( base . baseRefName === undefined ? { } : { baseRefName : base . baseRefName } ) ,
249- path : worktreePath ,
250- } ) ;
251- } ,
252- ) ;
253165
254166 // Build draining callbacks outside startup scope so they cannot retain full thread history.
255167 const makeRunControls = ( input : {
@@ -650,47 +562,16 @@ export const layer: Layer.Layer<
650562 return ;
651563 }
652564 }
653- const { worktreePath, branch } = projection . thread ;
654- if ( worktreePath !== null && branch !== null ) {
655- const exists = yield * fileSystem
656- . exists ( worktreePath )
657- . pipe ( Effect . orElseSucceed ( ( ) => true ) ) ;
658- if ( ! exists && ( yield * isManagedWorktreePath ( worktreePath ) ) ) {
659- const project = yield * projects . getById ( projection . thread . projectId ) . pipe (
660- Effect . map ( Option . getOrUndefined ) ,
661- Effect . orElseSucceed ( ( ) => undefined ) ,
662- ) ;
663- if ( project !== undefined ) {
664- yield * Effect . logWarning ( "provider turn start recreating missing worktree" , {
665- threadId : projection . thread . id ,
666- worktreePath,
667- branch,
668- } ) ;
669- const recreateResult = yield * Effect . result (
670- recreateMissingWorktree ( {
671- cwd : project . workspaceRoot ,
672- worktreePath,
673- branch,
674- } ) . pipe (
675- Effect . mapError ( ( cause ) =>
676- cause . _tag === "WorktreeRecreateNoBaseError"
677- ? cause
678- : new WorktreeRecreateError ( { worktreePath, branch, cause } ) ,
679- ) ,
680- ) ,
681- ) ;
682- if ( recreateResult . _tag === "Failure" ) {
683- if ( input . willRetry === true ) return yield * recreateResult . failure ;
684- yield * settleStartFailure ( {
685- signal : "worktree-recreate-failure" ,
686- title : "Couldn't recreate thread workspace" ,
687- error : recreateResult . failure ,
688- message : recreateResult . failure . message ,
689- } ) ;
690- return ;
691- }
692- }
693- }
565+ const recreateResult = yield * Effect . result ( restoreMissingWorktree ( projection . thread ) ) ;
566+ if ( recreateResult . _tag === "Failure" ) {
567+ if ( input . willRetry === true ) return yield * recreateResult . failure ;
568+ yield * settleStartFailure ( {
569+ signal : "worktree-recreate-failure" ,
570+ title : "Couldn't recreate thread workspace" ,
571+ error : recreateResult . failure ,
572+ message : recreateResult . failure . message ,
573+ } ) ;
574+ return ;
694575 }
695576 const selectInheritedBackgroundItems = (
696577 current : ProjectionStore . ProjectionRuntimeRecoveryState ,
0 commit comments