| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "strings" |
| 6 | |
| 7 | "reasonix/desktop/internal/workspacestate" |
| 8 | ) |
| 9 | |
| 10 | func manualCreationTargetError(err error) error { |
| 11 | code, message, retryable := "", "", false |
| 12 | switch { |
| 13 | case errors.Is(err, workspacestate.ErrCreationWorkspaceUnavailable): |
| 14 | code, message, retryable = "workspace_unavailable", "The project folder is unavailable. Creation will continue when it is accessible.", true |
| 15 | case errors.Is(err, workspacestate.ErrWorkspaceNotFound): |
| 16 | code, message = "workspace_removed", "The project is no longer registered. Open the project to create a new session." |
| 17 | case errors.Is(err, workspacestate.ErrCreationWorkspaceChanged): |
| 18 | code, message = "workspace_changed", "The project now points to a different folder. Open the intended project to create a new session." |
| 19 | case errors.Is(err, workspacestate.ErrCreationSessionInactive): |
| 20 | code, message = "creation_cancelled", "This session was archived or removed. Use New Session to start another conversation." |
| 21 | case errors.Is(err, workspacestate.ErrMutationConflict): |
| 22 | code, message = "creation_owner_conflict", "This creation request belongs to another session operation. Use New Session to start another conversation." |
| 23 | default: |
| 24 | return err |
| 25 | } |
| 26 | return &SessionOperationError{Code: code, Message: message, Retryable: retryable} |
| 27 | } |
| 28 | |
| 29 | // Older versions persisted safe-to-reconcile target changes as terminal errors. |
| 30 | // Run them once through the current resolver; genuine conflicts then receive |
| 31 | // specific terminal codes and will not enter an automatic retry loop. |
| 32 | func legacyManualCreationConflict(v ManualSessionCreationView) bool { |
| 33 | return v.Phase == "failed" && strings.HasPrefix(v.Error, "session_operation:target_changed:") |
| 34 | } |
| 35 | |
| 36 | func (a *App) replayManualCreation(req ManualSessionCreationRequest, v ManualSessionCreationView) (ManualSessionCreationView, error) { |
| 37 | root := desktopWorkspaceRoot(v.Scope, v.WorkspaceRoot) |
| 38 | requested := root |
| 39 | if req.Scope != "" || req.WorkspaceRoot != "" { |
| 40 | requested = desktopWorkspaceRoot(req.Scope, req.WorkspaceRoot) |
| 41 | } else if req.WorkspaceID != "" && req.WorkspaceID != v.WorkspaceID { |
| 42 | state, err := a.workspaceRegistry().Load(a.bootContext()) |
| 43 | if err != nil { |
| 44 | return v, err |
| 45 | } |
| 46 | w, ok := state.Workspaces[req.WorkspaceID] |
| 47 | if !ok { |
| 48 | return v, manualCreationTargetError(workspacestate.ErrWorkspaceNotFound) |
| 49 | } |
| 50 | requested = w.Root |
| 51 | } |
| 52 | same, err := sameDesktopPathStrict(root, requested) |
| 53 | if err != nil { |
| 54 | return v, manualCreationTargetError(workspacestate.ErrCreationWorkspaceUnavailable) |
| 55 | } |
| 56 | if !same { |
| 57 | return v, manualCreationTargetError(workspacestate.ErrCreationWorkspaceChanged) |
| 58 | } |
| 59 | if v.Phase == "reserved" || v.Phase == "starting" || legacyManualCreationConflict(v) { |
| 60 | a.creationManager().Ensure(v.OperationID, "begin", "") |
| 61 | } |
| 62 | return v, nil |
| 63 | } |
| 64 |