feat: add ability to run multiple agents from one prompt in isolated worktrees (#91)

* feat: WIP multirun implementation

* feat(chat): replace MindMap icon with ArrowsMerge

* feat(multirun): add base branch selection for worktrees

* feat(mobile): add multi-run launcher overlay
This commit is contained in:
Bohdan Triapitsyn
2026-01-01 14:59:51 +02:00
committed by GitHub
parent 2c1c960d5f
commit 10e64b4f90
13 changed files with 1041 additions and 34 deletions
@@ -8,6 +8,7 @@ import { SessionSidebar } from '@/components/session/SessionSidebar';
import { SessionDialogs } from '@/components/session/SessionDialogs';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { DiffWorkerProvider } from '@/contexts/DiffWorkerProvider';
import { MultiRunLauncher } from '@/components/multirun';
import { useUIStore } from '@/stores/useUIStore';
import { useUpdateStore } from '@/stores/useUpdateStore';
@@ -26,7 +27,11 @@ export const MainLayout: React.FC = () => {
setSessionSwitcherOpen,
isSettingsDialogOpen,
setSettingsDialogOpen,
isMultiRunLauncherOpen,
setMultiRunLauncherOpen,
multiRunLauncherPrefillPrompt,
} = useUIStore();
const { isMobile } = useDeviceInfo();
const [isDesktopRuntime, setIsDesktopRuntime] = React.useState<boolean>(() => {
if (typeof window === 'undefined') {
@@ -268,12 +273,12 @@ if (measuredInset === 0) {
{isMobile ? (
<>
{/* Mobile: Header + content + settings overlay */}
{!isSettingsDialogOpen && <Header />}
{/* Mobile: Header + content + overlays */}
{!(isSettingsDialogOpen || isMultiRunLauncherOpen) && <Header />}
<div
className={cn(
'flex flex-1 overflow-hidden bg-background',
isSettingsDialogOpen && 'hidden'
(isSettingsDialogOpen || isMultiRunLauncherOpen) && 'hidden'
)}
style={{ paddingTop: 'var(--oc-header-height, 56px)' }}
>
@@ -297,6 +302,19 @@ if (measuredInset === 0) {
<SessionSidebar mobileVariant />
</MobileOverlayPanel>
{/* Mobile multi-run launcher: full screen */}
{isMultiRunLauncherOpen && (
<div className="absolute inset-0 z-10 bg-background header-safe-area">
<ErrorBoundary>
<MultiRunLauncher
initialPrompt={multiRunLauncherPrefillPrompt}
onCreated={() => setMultiRunLauncherOpen(false)}
onCancel={() => setMultiRunLauncherOpen(false)}
/>
</ErrorBoundary>
</div>
)}
{/* Mobile settings: full screen */}
{isSettingsDialogOpen && (
<div className="absolute inset-0 z-10 bg-background header-safe-area">
@@ -315,7 +333,7 @@ if (measuredInset === 0) {
{/* Main content area */}
<div className="flex flex-1 flex-col overflow-hidden relative">
{/* Normal view: Header + content */}
<div className={cn('absolute inset-0 flex flex-col', isSettingsActive && 'invisible')}>
<div className={cn('absolute inset-0 flex flex-col', (isSettingsActive || isMultiRunLauncherOpen) && 'invisible')}>
<Header />
<div className="flex flex-1 overflow-hidden bg-background">
<main className="flex-1 overflow-hidden bg-background relative">
@@ -330,6 +348,19 @@ if (measuredInset === 0) {
</main>
</div>
</div>
{/* Multi-Run Launcher: replaces tabs content only */}
{isMultiRunLauncherOpen && (
<div className={cn('absolute inset-0 z-10', isDesktopRuntime ? 'bg-transparent' : 'bg-background')}>
<ErrorBoundary>
<MultiRunLauncher
initialPrompt={multiRunLauncherPrefillPrompt}
onCreated={() => setMultiRunLauncherOpen(false)}
onCancel={() => setMultiRunLauncherOpen(false)}
/>
</ErrorBoundary>
</div>
)}
</div>
{/* Settings view: full screen overlay */}