feat: move sessions to new worktrees
Add a root-session action that creates a generated worktree from the session directory's current branch, transfers uncommitted changes, and moves the parent session plus its descendants through OpenCode's control-plane API. Reuse existing project/worktree topology and quick-create behavior, keep the UI non-blocking, reconcile live and global session state across directories, and roll back partial moves and failed worktree creation safely. Split worktree bootstrap readiness into directory-created, git-ready, and setup-ready phases across web and VS Code. Session moves wait for Git readiness while existing setup-aware flows continue waiting for full setup completion, and worktree removal is serialized with active bootstrap tasks. Expose the move only for idle root sessions, show localized progress and explanatory tooltips in the sidebar, and keep pending/ready worktree metadata synchronized with authoritative session attachments to avoid stale setup indicators. Add coverage for control-plane payloads, session-state migration, bootstrap phase ordering and compatibility, removal races, progress metadata, and fast-ready attachment races.
This commit is contained in:
@@ -456,6 +456,12 @@ export const dict = {
|
||||
'sessions.sidebar.session.menu.copyLink': 'Copy link',
|
||||
'sessions.sidebar.session.menu.unshare': 'Unshare',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': 'Export Markdown',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': 'Move to new worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.success': 'Session moved to a new worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': 'Failed to move session to a new worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': 'Creates a new worktree from the current branch, transfers uncommitted changes, and moves this session and its sub-sessions there.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': 'Available when the session is idle. Stop or wait for the current activity to finish.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': 'This session is already being moved to a new worktree.',
|
||||
'sessions.sidebar.session.menu.runFusion': 'Run fusion',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': 'Open in Side Panel',
|
||||
'sessions.sidebar.session.actions.openInEditor': 'Open in Editor',
|
||||
@@ -476,6 +482,7 @@ export const dict = {
|
||||
'sessions.sidebar.session.status.active': 'Session active',
|
||||
'sessions.sidebar.session.status.unread': 'Unread updates',
|
||||
'sessions.sidebar.session.status.pinned': 'Pinned session',
|
||||
'sessions.sidebar.session.status.movingToWorktree': 'Moving session to a new worktree',
|
||||
'sessions.sidebar.session.status.permissionRequired': 'Permission required',
|
||||
'sessions.sidebar.session.subsessions.collapse': 'Collapse subsessions',
|
||||
'sessions.sidebar.session.subsessions.expand': 'Expand subsessions',
|
||||
|
||||
@@ -457,6 +457,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sessions.sidebar.session.menu.copyLink": "Copiar enlace",
|
||||
"sessions.sidebar.session.menu.unshare": "Dejar de compartir",
|
||||
"sessions.sidebar.session.menu.exportMarkdown": "Exportar Markdown",
|
||||
"sessions.sidebar.session.menu.moveToWorktree": "Mover a un worktree nuevo",
|
||||
"sessions.sidebar.session.moveToWorktree.success": "Sesión movida a un worktree nuevo",
|
||||
"sessions.sidebar.session.moveToWorktree.failed": "No se pudo mover la sesión a un worktree nuevo",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltip": "Crea un worktree nuevo desde la rama actual, transfiere los cambios sin confirmar y mueve allí esta sesión y sus subsesiones.",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltipBusy": "Disponible cuando la sesión está inactiva. Detén la actividad actual o espera a que termine.",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltipMoving": "Esta sesión ya se está moviendo a un worktree nuevo.",
|
||||
"sessions.sidebar.session.menu.runFusion": "Ejecutar fusion",
|
||||
"sessions.sidebar.session.menu.openInSidePanel": "Abrir en panel lateral",
|
||||
"sessions.sidebar.session.actions.openInEditor": "Abrir en el editor",
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sessions.sidebar.session.status.active": "Sesión activa",
|
||||
"sessions.sidebar.session.status.unread": "Actualizaciones no leídas",
|
||||
"sessions.sidebar.session.status.pinned": "Sesión anclada",
|
||||
"sessions.sidebar.session.status.movingToWorktree": "Moviendo la sesión a un worktree nuevo",
|
||||
"sessions.sidebar.session.status.permissionRequired": "Permiso requerido",
|
||||
"sessions.sidebar.session.subsessions.collapse": "Colapsar subsesiones",
|
||||
"sessions.sidebar.session.subsessions.expand": "Expandir subsesiones",
|
||||
|
||||
@@ -298,6 +298,12 @@ export const dict = {
|
||||
'sessions.sidebar.session.menu.copyLink': 'Copier le lien',
|
||||
'sessions.sidebar.session.menu.unshare': 'Annuler le partage',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': 'Exporter le Markdown',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': 'Déplacer vers un nouveau worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.success': 'Session déplacée vers un nouveau worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': 'Impossible de déplacer la session vers un nouveau worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': 'Crée un nouveau worktree depuis la branche actuelle, transfère les modifications non validées et y déplace cette session et ses sous-sessions.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': 'Disponible lorsque la session est inactive. Arrêtez l’activité en cours ou attendez sa fin.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': 'Cette session est déjà en cours de déplacement vers un nouveau worktree.',
|
||||
'sessions.sidebar.session.menu.runFusion': 'Exécuter la fusion',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': 'Ouvrir dans le panneau latéral',
|
||||
'sessions.sidebar.session.actions.openInEditor': 'Ouvrir dans l\'éditeur',
|
||||
@@ -318,6 +324,7 @@ export const dict = {
|
||||
'sessions.sidebar.session.status.active': 'Session active',
|
||||
'sessions.sidebar.session.status.unread': 'Mises à jour non lues',
|
||||
'sessions.sidebar.session.status.pinned': 'Session épinglée',
|
||||
'sessions.sidebar.session.status.movingToWorktree': 'Déplacement de la session vers un nouveau worktree',
|
||||
'sessions.sidebar.session.status.permissionRequired': 'Autorisation requise',
|
||||
'sessions.sidebar.session.subsessions.collapse': 'Réduire les sous-sessions',
|
||||
'sessions.sidebar.session.subsessions.expand': 'Développer les sous-sessions',
|
||||
|
||||
@@ -457,6 +457,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.menu.copyLink': 'リンクをコピー',
|
||||
'sessions.sidebar.session.menu.unshare': '共有解除',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': 'Markdownでエクスポート',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': '新しいworktreeへ移動',
|
||||
'sessions.sidebar.session.moveToWorktree.success': 'セッションを新しいworktreeへ移動しました',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': 'セッションを新しいworktreeへ移動できませんでした',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': '現在のブランチから新しいworktreeを作成し、未コミットの変更とこのセッションおよびサブセッションを移動します。',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': 'セッションがアイドル状態のときに利用できます。現在の処理を停止するか、完了するまでお待ちください。',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': 'このセッションはすでに新しいworktreeへ移動中です。',
|
||||
'sessions.sidebar.session.menu.runFusion': 'フュージョンを実行',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': 'サイドパネルで開く',
|
||||
'sessions.sidebar.session.actions.openInEditor': 'エディターで開く',
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.status.active': 'セッションアクティブ',
|
||||
'sessions.sidebar.session.status.unread': '未読の更新',
|
||||
'sessions.sidebar.session.status.pinned': 'ピン留めされたセッション',
|
||||
'sessions.sidebar.session.status.movingToWorktree': 'セッションを新しいworktreeへ移動中',
|
||||
'sessions.sidebar.session.status.permissionRequired': '権限が必要です',
|
||||
'sessions.sidebar.session.subsessions.collapse': 'サブセッションを折りたたむ',
|
||||
'sessions.sidebar.session.subsessions.expand': 'サブセッションを展開',
|
||||
|
||||
@@ -457,6 +457,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.menu.copyLink': '링크 복사',
|
||||
'sessions.sidebar.session.menu.unshare': '공유 해제',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': 'Markdown 내보내기',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': '새 worktree로 이동',
|
||||
'sessions.sidebar.session.moveToWorktree.success': '세션을 새 worktree로 이동했습니다',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': '세션을 새 worktree로 이동하지 못했습니다',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': '현재 브랜치에서 새 worktree를 만들고 커밋되지 않은 변경 사항과 이 세션 및 하위 세션을 이동합니다.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': '세션이 유휴 상태일 때 사용할 수 있습니다. 현재 작업을 중지하거나 완료될 때까지 기다리세요.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': '이 세션은 이미 새 worktree로 이동 중입니다.',
|
||||
'sessions.sidebar.session.menu.runFusion': 'fusion 실행',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': '사이드 패널에서 열기',
|
||||
'sessions.sidebar.session.actions.openInEditor': '편집기에서 열기',
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.status.active': '활성 세션',
|
||||
'sessions.sidebar.session.status.unread': '읽지 않은 업데이트',
|
||||
'sessions.sidebar.session.status.pinned': '고정된 세션',
|
||||
'sessions.sidebar.session.status.movingToWorktree': '세션을 새 worktree로 이동하는 중',
|
||||
'sessions.sidebar.session.status.permissionRequired': '권한 필요',
|
||||
'sessions.sidebar.session.subsessions.collapse': '하위 세션 접기',
|
||||
'sessions.sidebar.session.subsessions.expand': '하위 세션 펼치기',
|
||||
|
||||
@@ -262,6 +262,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.menu.copyLink': 'Kopiuj link',
|
||||
'sessions.sidebar.session.menu.unshare': 'Cofnij udostępnienie',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': 'Eksportuj Markdown',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': 'Przenieś do nowego worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.success': 'Sesja została przeniesiona do nowego worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': 'Nie udało się przenieść sesji do nowego worktree',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': 'Tworzy nowy worktree z bieżącej gałęzi, przenosi niezacommitowane zmiany oraz tę sesję i jej podsesje.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': 'Dostępne, gdy sesja jest bezczynna. Zatrzymaj bieżącą aktywność lub poczekaj na jej zakończenie.',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': 'Ta sesja jest już przenoszona do nowego worktree.',
|
||||
'sessions.sidebar.session.menu.runFusion': 'Uruchom fusion',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': 'Otwórz w panelu bocznym',
|
||||
'sessions.sidebar.session.actions.openInEditor': 'Otwórz w edytorze',
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.status.active': 'Sesja aktywna',
|
||||
'sessions.sidebar.session.status.unread': 'Nieprzeczytane aktualizacje',
|
||||
'sessions.sidebar.session.status.pinned': 'Przypięta sesja',
|
||||
'sessions.sidebar.session.status.movingToWorktree': 'Przenoszenie sesji do nowego worktree',
|
||||
'sessions.sidebar.session.status.permissionRequired': 'Wymagane uprawnienie',
|
||||
'sessions.sidebar.session.subsessions.collapse': 'Zwiń pod-sesje',
|
||||
'sessions.sidebar.session.subsessions.expand': 'Rozwiń pod-sesje',
|
||||
|
||||
@@ -457,6 +457,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sessions.sidebar.session.menu.copyLink": "Copiar link",
|
||||
"sessions.sidebar.session.menu.unshare": "Parar de compartilhar",
|
||||
"sessions.sidebar.session.menu.exportMarkdown": "Exportar Markdown",
|
||||
"sessions.sidebar.session.menu.moveToWorktree": "Mover para um novo worktree",
|
||||
"sessions.sidebar.session.moveToWorktree.success": "Sessão movida para um novo worktree",
|
||||
"sessions.sidebar.session.moveToWorktree.failed": "Não foi possível mover a sessão para um novo worktree",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltip": "Cria um novo worktree a partir da branch atual, transfere alterações não commitadas e move esta sessão e suas subsessões para lá.",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltipBusy": "Disponível quando a sessão está ociosa. Interrompa a atividade atual ou aguarde sua conclusão.",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltipMoving": "Esta sessão já está sendo movida para um novo worktree.",
|
||||
"sessions.sidebar.session.menu.runFusion": "Executar fusion",
|
||||
"sessions.sidebar.session.menu.openInSidePanel": "Abrir no painel lateral",
|
||||
"sessions.sidebar.session.actions.openInEditor": "Abrir no editor",
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sessions.sidebar.session.status.active": "Sessão ativa",
|
||||
"sessions.sidebar.session.status.unread": "Atualizações não lidas",
|
||||
"sessions.sidebar.session.status.pinned": "Sessão fixada",
|
||||
"sessions.sidebar.session.status.movingToWorktree": "Movendo a sessão para um novo worktree",
|
||||
"sessions.sidebar.session.status.permissionRequired": "Permissão obrigatória",
|
||||
"sessions.sidebar.session.subsessions.collapse": "Recolher subsessões",
|
||||
"sessions.sidebar.session.subsessions.expand": "Expandir subsessões",
|
||||
|
||||
@@ -457,6 +457,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sessions.sidebar.session.menu.copyLink": "Копіювати посилання",
|
||||
"sessions.sidebar.session.menu.unshare": "Скасувати спільний доступ",
|
||||
"sessions.sidebar.session.menu.exportMarkdown": "Експорт Markdown",
|
||||
"sessions.sidebar.session.menu.moveToWorktree": "Перенести в новий worktree",
|
||||
"sessions.sidebar.session.moveToWorktree.success": "Сесію перенесено в новий worktree",
|
||||
"sessions.sidebar.session.moveToWorktree.failed": "Не вдалося перенести сесію в новий worktree",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltip": "Створює новий worktree з поточної гілки, переносить незакомічені зміни та переміщує туди цю сесію і її підсесії.",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltipBusy": "Доступно, коли сесія неактивна. Зупиніть поточну активність або дочекайтеся її завершення.",
|
||||
"sessions.sidebar.session.moveToWorktree.tooltipMoving": "Ця сесія вже переноситься в новий worktree.",
|
||||
"sessions.sidebar.session.menu.runFusion": "Запустити fusion",
|
||||
"sessions.sidebar.session.menu.openInSidePanel": "Відкрити на бічній панелі",
|
||||
"sessions.sidebar.session.actions.openInEditor": "Відкрити в редакторі",
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sessions.sidebar.session.status.active": "Сесія активний",
|
||||
"sessions.sidebar.session.status.unread": "Непрочитані оновлення",
|
||||
"sessions.sidebar.session.status.pinned": "Закріплений сесія",
|
||||
"sessions.sidebar.session.status.movingToWorktree": "Перенесення сесії в новий worktree",
|
||||
"sessions.sidebar.session.status.permissionRequired": "Потрібен дозвіл",
|
||||
"sessions.sidebar.session.subsessions.collapse": "Згорнути підсесії",
|
||||
"sessions.sidebar.session.subsessions.expand": "Розгорнути підсесії",
|
||||
|
||||
@@ -457,6 +457,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.menu.copyLink': '复制链接',
|
||||
'sessions.sidebar.session.menu.unshare': '取消分享',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': '导出 Markdown',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': '移至新工作树',
|
||||
'sessions.sidebar.session.moveToWorktree.success': '会话已移至新工作树',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': '无法将会话移至新工作树',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': '从当前分支创建新工作树,转移未提交的更改,并将此会话及其子会话移至其中。',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': '仅在会话空闲时可用。请停止当前活动或等待其完成。',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': '此会话已在移至新工作树。',
|
||||
'sessions.sidebar.session.menu.runFusion': '运行融合',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': '在侧边面板中打开',
|
||||
'sessions.sidebar.session.actions.openInEditor': '在编辑器中打开',
|
||||
@@ -477,6 +483,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.status.active': '会话活跃中',
|
||||
'sessions.sidebar.session.status.unread': '有未读更新',
|
||||
'sessions.sidebar.session.status.pinned': '已置顶会话',
|
||||
'sessions.sidebar.session.status.movingToWorktree': '正在将会话移至新工作树',
|
||||
'sessions.sidebar.session.status.permissionRequired': '需要权限',
|
||||
'sessions.sidebar.session.subsessions.collapse': '折叠子会话',
|
||||
'sessions.sidebar.session.subsessions.expand': '展开子会话',
|
||||
|
||||
@@ -470,6 +470,12 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.menu.copyLink': '複製連結',
|
||||
'sessions.sidebar.session.menu.unshare': '取消分享',
|
||||
'sessions.sidebar.session.menu.exportMarkdown': '匯出 Markdown',
|
||||
'sessions.sidebar.session.menu.moveToWorktree': '移至新工作樹',
|
||||
'sessions.sidebar.session.moveToWorktree.success': '工作階段已移至新工作樹',
|
||||
'sessions.sidebar.session.moveToWorktree.failed': '無法將工作階段移至新工作樹',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltip': '從目前分支建立新工作樹,轉移未提交的變更,並將此工作階段及其子工作階段移至其中。',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipBusy': '僅在工作階段閒置時可用。請停止目前活動或等待其完成。',
|
||||
'sessions.sidebar.session.moveToWorktree.tooltipMoving': '此工作階段已在移至新工作樹。',
|
||||
'sessions.sidebar.session.menu.runFusion': '執行 fusion',
|
||||
'sessions.sidebar.session.menu.openInSidePanel': '在側邊面板中開啟',
|
||||
'sessions.sidebar.session.actions.openInEditor': '在編輯器中開啟',
|
||||
@@ -490,6 +496,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sessions.sidebar.session.status.active': '會話活躍中',
|
||||
'sessions.sidebar.session.status.unread': '有未讀更新',
|
||||
'sessions.sidebar.session.status.pinned': '已釘選會話',
|
||||
'sessions.sidebar.session.status.movingToWorktree': '正在將會話移至新工作樹',
|
||||
'sessions.sidebar.session.status.permissionRequired': '需要權限',
|
||||
'sessions.sidebar.session.subsessions.collapse': '摺疊子會話',
|
||||
'sessions.sidebar.session.subsessions.expand': '展開子會話',
|
||||
|
||||
Reference in New Issue
Block a user