1- import { NamedError } from "@opencode-ai/core/util/error"
21import { Global } from "@opencode-ai/core/global"
32import { InstanceLayer } from "@/project/instance-layer"
43import { InstanceStore } from "@/project/instance-store"
@@ -64,33 +63,48 @@ export const ResetInput = Schema.Struct({
6463} ) . annotate ( { identifier : "WorktreeResetInput" } )
6564export type ResetInput = Schema . Schema . Type < typeof ResetInput >
6665
67- export const NotGitError = NamedError . create ( "WorktreeNotGitError" , {
66+ export class NotGitError extends Schema . TaggedErrorClass < NotGitError > ( ) ( "WorktreeNotGitError" , {
6867 message : Schema . String ,
69- } )
68+ } ) { }
7069
71- export const NameGenerationFailedError = NamedError . create ( "WorktreeNameGenerationFailedError" , {
72- message : Schema . String ,
73- } )
70+ export class NameGenerationFailedError extends Schema . TaggedErrorClass < NameGenerationFailedError > ( ) (
71+ "WorktreeNameGenerationFailedError" ,
72+ {
73+ message : Schema . String ,
74+ } ,
75+ ) { }
7476
75- export const CreateFailedError = NamedError . create ( "WorktreeCreateFailedError" , {
77+ export class CreateFailedError extends Schema . TaggedErrorClass < CreateFailedError > ( ) ( "WorktreeCreateFailedError" , {
7678 message : Schema . String ,
77- } )
79+ } ) { }
7880
79- export const StartCommandFailedError = NamedError . create ( "WorktreeStartCommandFailedError" , {
80- message : Schema . String ,
81- } )
81+ export class StartCommandFailedError extends Schema . TaggedErrorClass < StartCommandFailedError > ( ) (
82+ "WorktreeStartCommandFailedError" ,
83+ {
84+ message : Schema . String ,
85+ } ,
86+ ) { }
8287
83- export const RemoveFailedError = NamedError . create ( "WorktreeRemoveFailedError" , {
88+ export class RemoveFailedError extends Schema . TaggedErrorClass < RemoveFailedError > ( ) ( "WorktreeRemoveFailedError" , {
8489 message : Schema . String ,
85- } )
90+ } ) { }
8691
87- export const ResetFailedError = NamedError . create ( "WorktreeResetFailedError" , {
92+ export class ResetFailedError extends Schema . TaggedErrorClass < ResetFailedError > ( ) ( "WorktreeResetFailedError" , {
8893 message : Schema . String ,
89- } )
94+ } ) { }
9095
91- export const ListFailedError = NamedError . create ( "WorktreeListFailedError" , {
96+ export class ListFailedError extends Schema . TaggedErrorClass < ListFailedError > ( ) ( "WorktreeListFailedError" , {
9297 message : Schema . String ,
93- } )
98+ } ) { }
99+
100+ export type Error =
101+ | NotGitError
102+ | NameGenerationFailedError
103+ | CreateFailedError
104+ | StartCommandFailedError
105+ | RemoveFailedError
106+ | ResetFailedError
107+ | ListFailedError
94108
95109function slugify ( input : string ) {
96110 return input
@@ -121,12 +135,12 @@ function failedRemoves(...chunks: string[]) {
121135// ---------------------------------------------------------------------------
122136
123137export interface Interface {
124- readonly makeWorktreeInfo : ( options ?: { name ?: string ; detached ?: boolean } ) => Effect . Effect < Info >
125- readonly createFromInfo : ( info : Info , startCommand ?: string ) => Effect . Effect < void >
126- readonly create : ( input ?: CreateInput ) => Effect . Effect < Info >
127- readonly list : ( ) => Effect . Effect < ( Omit < Info , "branch" > & { branch ?: string } ) [ ] >
128- readonly remove : ( input : RemoveInput ) => Effect . Effect < boolean >
129- readonly reset : ( input : ResetInput ) => Effect . Effect < boolean >
138+ readonly makeWorktreeInfo : ( options ?: { name ?: string ; detached ?: boolean } ) => Effect . Effect < Info , Error >
139+ readonly createFromInfo : ( info : Info , startCommand ?: string ) => Effect . Effect < void , Error >
140+ readonly create : ( input ?: CreateInput ) => Effect . Effect < Info , Error >
141+ readonly list : ( ) => Effect . Effect < ( Omit < Info , "branch" > & { branch ?: string } ) [ ] , Error >
142+ readonly remove : ( input : RemoveInput ) => Effect . Effect < boolean , Error >
143+ readonly reset : ( input : ResetInput ) => Effect . Effect < boolean , Error >
130144}
131145
132146export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/Worktree" ) { }
@@ -193,7 +207,7 @@ export const layer: Layer.Layer<
193207
194208 return { name, directory, ...( branch ? { branch } : { } ) }
195209 }
196- throw new NameGenerationFailedError ( { message : "Failed to generate a unique worktree name" } )
210+ return yield * new NameGenerationFailedError ( { message : "Failed to generate a unique worktree name" } )
197211 } )
198212
199213 const makeWorktreeInfo = Effect . fn ( "Worktree.makeWorktreeInfo" ) ( function * ( input ?: {
@@ -202,7 +216,7 @@ export const layer: Layer.Layer<
202216 } ) {
203217 const ctx = yield * InstanceState . context
204218 if ( ctx . project . vcs !== "git" ) {
205- throw new NotGitError ( { message : "Worktrees are only supported for git projects" } )
219+ return yield * new NotGitError ( { message : "Worktrees are only supported for git projects" } )
206220 }
207221
208222 const root = pathSvc . join ( Global . Path . data , "worktree" , ctx . project . id )
@@ -220,7 +234,7 @@ export const layer: Layer.Layer<
220234 { cwd : ctx . worktree } ,
221235 )
222236 if ( created . code !== 0 ) {
223- throw new CreateFailedError ( { message : created . stderr || created . text || "Failed to create git worktree" } )
237+ return yield * new CreateFailedError ( { message : created . stderr || created . text || "Failed to create git worktree" } )
224238 }
225239
226240 yield * project . addSandbox ( ctx . project . id , info . directory ) . pipe ( Effect . catch ( ( ) => Effect . void ) )
@@ -336,7 +350,7 @@ export const layer: Layer.Layer<
336350
337351 const result = yield * git ( [ "worktree" , "list" , "--porcelain" ] , { cwd : ctx . worktree } )
338352 if ( result . code !== 0 ) {
339- throw new ListFailedError ( { message : result . stderr || result . text || "Failed to read git worktrees" } )
353+ return yield * new ListFailedError ( { message : result . stderr || result . text || "Failed to read git worktrees" } )
340354 }
341355
342356 const primary = yield * canonical ( ctx . worktree )
@@ -364,27 +378,27 @@ export const layer: Layer.Layer<
364378 }
365379
366380 function cleanDirectory ( target : string ) {
367- return Effect . promise ( ( ) =>
368- import ( "fs/promises" )
369- . then ( ( fsp ) => fsp . rm ( target , { recursive : true , force : true , maxRetries : 5 , retryDelay : 100 } ) )
370- . catch ( ( error ) => {
371- const message = errorMessage ( error )
372- throw new RemoveFailedError ( { message : message || "Failed to remove git worktree directory" } )
373- } ) ,
374- )
381+ return Effect . tryPromise ( {
382+ try : ( ) =>
383+ import ( "fs/promises" ) . then ( ( fsp ) =>
384+ fsp . rm ( target , { recursive : true , force : true , maxRetries : 5 , retryDelay : 100 } ) ,
385+ ) ,
386+ catch : ( error ) =>
387+ new RemoveFailedError ( { message : errorMessage ( error ) || "Failed to remove git worktree directory" } ) ,
388+ } )
375389 }
376390
377391 const remove = Effect . fn ( "Worktree.remove" ) ( function * ( input : RemoveInput ) {
378392 const ctx = yield * InstanceState . context
379393 if ( ctx . project . vcs !== "git" ) {
380- throw new NotGitError ( { message : "Worktrees are only supported for git projects" } )
394+ return yield * new NotGitError ( { message : "Worktrees are only supported for git projects" } )
381395 }
382396
383397 const directory = yield * canonical ( input . directory )
384398
385399 const list = yield * git ( [ "worktree" , "list" , "--porcelain" ] , { cwd : ctx . worktree } )
386400 if ( list . code !== 0 ) {
387- throw new RemoveFailedError ( { message : list . stderr || list . text || "Failed to read git worktrees" } )
401+ return yield * new RemoveFailedError ( { message : list . stderr || list . text || "Failed to read git worktrees" } )
388402 }
389403
390404 const entries = parseWorktreeList ( list . text )
@@ -404,14 +418,14 @@ export const layer: Layer.Layer<
404418 if ( removed . code !== 0 ) {
405419 const next = yield * git ( [ "worktree" , "list" , "--porcelain" ] , { cwd : ctx . worktree } )
406420 if ( next . code !== 0 ) {
407- throw new RemoveFailedError ( {
421+ return yield * new RemoveFailedError ( {
408422 message : removed . stderr || removed . text || next . stderr || next . text || "Failed to remove git worktree" ,
409423 } )
410424 }
411425
412426 const stale = yield * locateWorktree ( parseWorktreeList ( next . text ) , directory )
413427 if ( stale ?. path ) {
414- throw new RemoveFailedError ( { message : removed . stderr || removed . text || "Failed to remove git worktree" } )
428+ return yield * new RemoveFailedError ( { message : removed . stderr || removed . text || "Failed to remove git worktree" } )
415429 }
416430 }
417431
@@ -421,7 +435,7 @@ export const layer: Layer.Layer<
421435 if ( branch ) {
422436 const deleted = yield * git ( [ "branch" , "-D" , branch ] , { cwd : ctx . worktree } )
423437 if ( deleted . code !== 0 ) {
424- throw new RemoveFailedError ( {
438+ return yield * new RemoveFailedError ( {
425439 message : deleted . stderr || deleted . text || "Failed to delete worktree branch" ,
426440 } )
427441 }
@@ -436,7 +450,7 @@ export const layer: Layer.Layer<
436450 error : ( r : GitResult ) => Error ,
437451 ) {
438452 const result = yield * git ( args , opts )
439- if ( result . code !== 0 ) throw error ( result )
453+ if ( result . code !== 0 ) return yield * error ( result )
440454 return result
441455 } )
442456
@@ -511,30 +525,30 @@ export const layer: Layer.Layer<
511525 const reset = Effect . fn ( "Worktree.reset" ) ( function * ( input : ResetInput ) {
512526 const ctx = yield * InstanceState . context
513527 if ( ctx . project . vcs !== "git" ) {
514- throw new NotGitError ( { message : "Worktrees are only supported for git projects" } )
528+ return yield * new NotGitError ( { message : "Worktrees are only supported for git projects" } )
515529 }
516530
517531 const directory = yield * canonical ( input . directory )
518532 const primary = yield * canonical ( ctx . worktree )
519533 if ( directory === primary ) {
520- throw new ResetFailedError ( { message : "Cannot reset the primary workspace" } )
534+ return yield * new ResetFailedError ( { message : "Cannot reset the primary workspace" } )
521535 }
522536
523537 const list = yield * git ( [ "worktree" , "list" , "--porcelain" ] , { cwd : ctx . worktree } )
524538 if ( list . code !== 0 ) {
525- throw new ResetFailedError ( { message : list . stderr || list . text || "Failed to read git worktrees" } )
539+ return yield * new ResetFailedError ( { message : list . stderr || list . text || "Failed to read git worktrees" } )
526540 }
527541
528542 const entry = yield * locateWorktree ( parseWorktreeList ( list . text ) , directory )
529543 if ( ! entry ?. path ) {
530- throw new ResetFailedError ( { message : "Worktree not found" } )
544+ return yield * new ResetFailedError ( { message : "Worktree not found" } )
531545 }
532546
533547 const worktreePath = entry . path
534548
535549 const base = yield * gitSvc . defaultBranch ( ctx . worktree )
536550 if ( ! base ) {
537- throw new ResetFailedError ( { message : "Default branch not found" } )
551+ return yield * new ResetFailedError ( { message : "Default branch not found" } )
538552 }
539553
540554 const sep = base . ref . indexOf ( "/" )
@@ -556,7 +570,7 @@ export const layer: Layer.Layer<
556570
557571 const cleanResult = yield * sweep ( worktreePath )
558572 if ( cleanResult . code !== 0 ) {
559- throw new ResetFailedError ( { message : cleanResult . stderr || cleanResult . text || "Failed to clean worktree" } )
573+ return yield * new ResetFailedError ( { message : cleanResult . stderr || cleanResult . text || "Failed to clean worktree" } )
560574 }
561575
562576 yield * gitExpect (
@@ -579,11 +593,11 @@ export const layer: Layer.Layer<
579593
580594 const status = yield * git ( [ "-c" , "core.fsmonitor=false" , "status" , "--porcelain=v1" ] , { cwd : worktreePath } )
581595 if ( status . code !== 0 ) {
582- throw new ResetFailedError ( { message : status . stderr || status . text || "Failed to read git status" } )
596+ return yield * new ResetFailedError ( { message : status . stderr || status . text || "Failed to read git status" } )
583597 }
584598
585599 if ( status . text . trim ( ) ) {
586- throw new ResetFailedError ( { message : `Worktree reset left local changes:\n${ status . text . trim ( ) } ` } )
600+ return yield * new ResetFailedError ( { message : `Worktree reset left local changes:\n${ status . text . trim ( ) } ` } )
587601 }
588602
589603 yield * runStartScripts ( worktreePath , { projectID : ctx . project . id } ) . pipe (
0 commit comments