mirror of
https://github.com/getpaseo/paseo.git
synced 2026-08-14 20:32:46 +00:00
refactor: centralize host route builders and add dedicated new-agent route
This commit is contained in:
@@ -45,6 +45,7 @@ import {
|
|||||||
} from "@/utils/os-notifications";
|
} from "@/utils/os-notifications";
|
||||||
import { buildNotificationRoute } from "@/utils/notification-routing";
|
import { buildNotificationRoute } from "@/utils/notification-routing";
|
||||||
import {
|
import {
|
||||||
|
buildHostRootRoute,
|
||||||
parseHostAgentRouteFromPathname,
|
parseHostAgentRouteFromPathname,
|
||||||
parseHostWorkspaceTabRouteFromPathname,
|
parseHostWorkspaceTabRouteFromPathname,
|
||||||
} from "@/utils/host-routes";
|
} from "@/utils/host-routes";
|
||||||
@@ -350,7 +351,7 @@ function OfferLinkListener({
|
|||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const serverId = (profile as any)?.serverId;
|
const serverId = (profile as any)?.serverId;
|
||||||
if (typeof serverId !== "string" || !serverId) return;
|
if (typeof serverId !== "string" || !serverId) return;
|
||||||
router.replace(`/h/${encodeURIComponent(serverId)}` as any);
|
router.replace(buildHostRootRoute(serverId) as any);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
@@ -471,14 +472,14 @@ export default function RootLayout() {
|
|||||||
>
|
>
|
||||||
<Stack.Screen name="index" />
|
<Stack.Screen name="index" />
|
||||||
<Stack.Screen name="settings" />
|
<Stack.Screen name="settings" />
|
||||||
<Stack.Screen name="h/[serverId]/workspace/[workspaceId]/index" />
|
<Stack.Screen name="h/[serverId]/workspace/[workspaceId]" />
|
||||||
<Stack.Screen name="h/[serverId]/workspace/[workspaceId]/tab/[tabId]" />
|
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="h/[serverId]/agent/[agentId]"
|
name="h/[serverId]/agent/[agentId]"
|
||||||
options={{ gestureEnabled: false }}
|
options={{ gestureEnabled: false }}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen name="h/[serverId]/index" />
|
<Stack.Screen name="h/[serverId]/index" />
|
||||||
<Stack.Screen name="h/[serverId]/agents" />
|
<Stack.Screen name="h/[serverId]/agents" />
|
||||||
|
<Stack.Screen name="h/[serverId]/new-agent" />
|
||||||
<Stack.Screen name="h/[serverId]/settings" />
|
<Stack.Screen name="h/[serverId]/settings" />
|
||||||
<Stack.Screen name="pair-scan" />
|
<Stack.Screen name="pair-scan" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useLocalSearchParams, useRouter } from "expo-router";
|
|||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import {
|
import {
|
||||||
|
buildHostRootRoute,
|
||||||
buildHostWorkspaceAgentTabRoute,
|
buildHostWorkspaceAgentTabRoute,
|
||||||
} from "@/utils/host-routes";
|
} from "@/utils/host-routes";
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ export default function HostAgentReadyRoute() {
|
|||||||
}
|
}
|
||||||
if (!client || !isConnected) {
|
if (!client || !isConnected) {
|
||||||
redirectedRef.current = true;
|
redirectedRef.current = true;
|
||||||
router.replace(`/h/${encodeURIComponent(serverId)}` as any);
|
router.replace(buildHostRootRoute(serverId) as any);
|
||||||
}
|
}
|
||||||
}, [agentCwd, agentId, client, isConnected, router, serverId]);
|
}, [agentCwd, agentId, client, isConnected, router, serverId]);
|
||||||
|
|
||||||
@@ -79,14 +80,14 @@ export default function HostAgentReadyRoute() {
|
|||||||
router.replace(buildHostWorkspaceAgentTabRoute(serverId, cwd, agentId) as any);
|
router.replace(buildHostWorkspaceAgentTabRoute(serverId, cwd, agentId) as any);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.replace(`/h/${encodeURIComponent(serverId)}` as any);
|
router.replace(buildHostRootRoute(serverId) as any);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (cancelled || redirectedRef.current) {
|
if (cancelled || redirectedRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
redirectedRef.current = true;
|
redirectedRef.current = true;
|
||||||
router.replace(`/h/${encodeURIComponent(serverId)}` as any);
|
router.replace(buildHostRootRoute(serverId) as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
9
packages/app/src/app/h/[serverId]/new-agent.tsx
Normal file
9
packages/app/src/app/h/[serverId]/new-agent.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useLocalSearchParams } from "expo-router";
|
||||||
|
import { DraftAgentScreen } from "@/screens/agent/draft-agent-screen";
|
||||||
|
|
||||||
|
export default function HostNewAgentRoute() {
|
||||||
|
const params = useLocalSearchParams<{ serverId?: string }>();
|
||||||
|
const serverId = typeof params.serverId === "string" ? params.serverId : "";
|
||||||
|
|
||||||
|
return <DraftAgentScreen forcedServerId={serverId} />;
|
||||||
|
}
|
||||||
@@ -1,20 +1,32 @@
|
|||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams, usePathname } from "expo-router";
|
||||||
import { WorkspaceScreen } from "@/screens/workspace/workspace-screen";
|
import { WorkspaceScreen } from "@/screens/workspace/workspace-screen";
|
||||||
|
import {
|
||||||
|
parseHostWorkspaceRouteFromPathname,
|
||||||
|
parseHostWorkspaceTabRouteFromPathname,
|
||||||
|
} from "@/utils/host-routes";
|
||||||
|
|
||||||
|
function readNonEmptyParam(value: unknown): string | null {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
|
}
|
||||||
|
|
||||||
export default function HostWorkspaceLayout() {
|
export default function HostWorkspaceLayout() {
|
||||||
const params = useLocalSearchParams<{
|
const pathname = usePathname();
|
||||||
serverId?: string;
|
const params = useLocalSearchParams<{ tabId?: string }>();
|
||||||
workspaceId?: string;
|
const tabRoute = parseHostWorkspaceTabRouteFromPathname(pathname);
|
||||||
tabId?: string;
|
const activeRoute = tabRoute ?? parseHostWorkspaceRouteFromPathname(pathname);
|
||||||
}>();
|
const serverId = activeRoute?.serverId ?? "";
|
||||||
|
const workspaceId = activeRoute?.workspaceId ?? "";
|
||||||
const tabId = typeof params.tabId === "string" ? params.tabId : "";
|
const routeTabId = tabRoute?.tabId ?? readNonEmptyParam(params.tabId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceScreen
|
<WorkspaceScreen
|
||||||
serverId={typeof params.serverId === "string" ? params.serverId : ""}
|
serverId={serverId}
|
||||||
workspaceId={typeof params.workspaceId === "string" ? params.workspaceId : ""}
|
workspaceId={workspaceId}
|
||||||
routeTabId={tabId || null}
|
routeTabId={routeTabId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useUnistyles } from "react-native-unistyles";
|
|||||||
import { DraftAgentScreen } from "@/screens/agent/draft-agent-screen";
|
import { DraftAgentScreen } from "@/screens/agent/draft-agent-screen";
|
||||||
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useFormPreferences } from "@/hooks/use-form-preferences";
|
import { useFormPreferences } from "@/hooks/use-form-preferences";
|
||||||
|
import { buildHostRootRoute } from "@/utils/host-routes";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -44,7 +45,7 @@ export default function Index() {
|
|||||||
if (!targetServerId) {
|
if (!targetServerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.replace(`/h/${encodeURIComponent(targetServerId)}` as any);
|
router.replace(buildHostRootRoute(targetServerId) as any);
|
||||||
}, [preferencesLoading, registryLoading, router, targetServerId]);
|
}, [preferencesLoading, registryLoading, router, targetServerId]);
|
||||||
|
|
||||||
if (registryLoading || preferencesLoading) {
|
if (registryLoading || preferencesLoading) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { decodeOfferFragmentPayload, normalizeHostPort } from "@/utils/daemon-en
|
|||||||
import { probeConnection } from "@/utils/test-daemon-connection";
|
import { probeConnection } from "@/utils/test-daemon-connection";
|
||||||
import { ConnectionOfferSchema } from "@server/shared/connection-offer";
|
import { ConnectionOfferSchema } from "@server/shared/connection-offer";
|
||||||
import {
|
import {
|
||||||
|
buildHostRootRoute,
|
||||||
buildHostSettingsRoute,
|
buildHostSettingsRoute,
|
||||||
} from "@/utils/host-routes";
|
} from "@/utils/host-routes";
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@ export default function PairScanScreen() {
|
|||||||
const returnToSource = useCallback(
|
const returnToSource = useCallback(
|
||||||
(serverId: string) => {
|
(serverId: string) => {
|
||||||
if (source === "onboarding") {
|
if (source === "onboarding") {
|
||||||
router.replace(`/h/${encodeURIComponent(serverId)}` as any);
|
router.replace(buildHostRootRoute(serverId) as any);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (source === "editHost" && targetServerId) {
|
if (source === "editHost" && targetServerId) {
|
||||||
|
|||||||
@@ -21,21 +21,11 @@ import { useTauriDragHandlers, useTrafficLightPadding } from '@/utils/tauri-wind
|
|||||||
import { Combobox } from '@/components/ui/combobox'
|
import { Combobox } from '@/components/ui/combobox'
|
||||||
import { useDaemonRegistry } from '@/contexts/daemon-registry-context'
|
import { useDaemonRegistry } from '@/contexts/daemon-registry-context'
|
||||||
import { getHostRuntimeStore } from '@/runtime/host-runtime'
|
import { getHostRuntimeStore } from '@/runtime/host-runtime'
|
||||||
import { useSessionStore } from '@/stores/session-store'
|
|
||||||
import { formatConnectionStatus } from '@/utils/daemons'
|
import { formatConnectionStatus } from '@/utils/daemons'
|
||||||
import { HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE } from '@/constants/layout'
|
import { HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE } from '@/constants/layout'
|
||||||
import {
|
|
||||||
checkoutStatusQueryKey,
|
|
||||||
type CheckoutStatusPayload,
|
|
||||||
} from '@/hooks/use-checkout-status-query'
|
|
||||||
import { queryClient } from '@/query/query-client'
|
|
||||||
import {
|
|
||||||
buildNewAgentRoute,
|
|
||||||
resolveNewAgentWorkingDir,
|
|
||||||
resolveSelectedAgentForNewAgent,
|
|
||||||
} from '@/utils/new-agent-routing'
|
|
||||||
import {
|
import {
|
||||||
buildHostAgentsRoute,
|
buildHostAgentsRoute,
|
||||||
|
buildHostNewAgentRoute,
|
||||||
buildHostSettingsRoute,
|
buildHostSettingsRoute,
|
||||||
mapPathnameToServer,
|
mapPathnameToServer,
|
||||||
parseServerIdFromPathname,
|
parseServerIdFromPathname,
|
||||||
@@ -47,7 +37,7 @@ interface LeftSidebarProps {
|
|||||||
selectedAgentId?: string
|
selectedAgentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
export function LeftSidebar({ selectedAgentId: _selectedAgentId }: LeftSidebarProps) {
|
||||||
const { theme } = useUnistyles()
|
const { theme } = useUnistyles()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
||||||
@@ -79,13 +69,24 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
.join('|')
|
.join('|')
|
||||||
)
|
)
|
||||||
const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname])
|
const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname])
|
||||||
const activeServerId = activeServerIdFromPath ?? daemons[0]?.serverId ?? null
|
const activeDaemon = useMemo(() => {
|
||||||
|
if (daemons.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (activeServerIdFromPath) {
|
||||||
|
const routeMatch = daemons.find((entry) => entry.serverId === activeServerIdFromPath)
|
||||||
|
if (routeMatch) {
|
||||||
|
return routeMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return daemons[0] ?? null
|
||||||
|
}, [activeServerIdFromPath, daemons])
|
||||||
|
const activeServerId = activeDaemon?.serverId ?? null
|
||||||
const activeHostLabel = useMemo(() => {
|
const activeHostLabel = useMemo(() => {
|
||||||
if (!activeServerId) return 'No host'
|
if (!activeDaemon) return 'No host'
|
||||||
const daemon = daemons.find((entry) => entry.serverId === activeServerId)
|
const trimmed = activeDaemon.label?.trim()
|
||||||
const trimmed = daemon?.label?.trim()
|
return trimmed && trimmed.length > 0 ? trimmed : activeDaemon.serverId
|
||||||
return trimmed && trimmed.length > 0 ? trimmed : activeServerId
|
}, [activeDaemon])
|
||||||
}, [activeServerId, daemons])
|
|
||||||
const activeHostStatus = activeServerId
|
const activeHostStatus = activeServerId
|
||||||
? (runtime.getSnapshot(activeServerId)?.connectionStatus ?? 'connecting')
|
? (runtime.getSnapshot(activeServerId)?.connectionStatus ?? 'connecting')
|
||||||
: 'idle'
|
: 'idle'
|
||||||
@@ -150,33 +151,11 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
}, [closeToAgent])
|
}, [closeToAgent])
|
||||||
|
|
||||||
const handleCreateAgentClean = useCallback(() => {
|
const handleCreateAgentClean = useCallback(() => {
|
||||||
let targetServerId = activeServerId
|
if (!activeServerId) {
|
||||||
let targetWorkingDir: string | null = null
|
|
||||||
|
|
||||||
const selectedAgent = resolveSelectedAgentForNewAgent({
|
|
||||||
pathname,
|
|
||||||
selectedAgentId,
|
|
||||||
})
|
|
||||||
if (selectedAgent) {
|
|
||||||
targetServerId = selectedAgent.serverId
|
|
||||||
const agent = useSessionStore
|
|
||||||
.getState()
|
|
||||||
.sessions[selectedAgent.serverId]?.agents?.get(selectedAgent.agentId)
|
|
||||||
const cwd = agent?.cwd?.trim()
|
|
||||||
if (cwd) {
|
|
||||||
const checkout =
|
|
||||||
queryClient.getQueryData<CheckoutStatusPayload>(
|
|
||||||
checkoutStatusQueryKey(selectedAgent.serverId, cwd)
|
|
||||||
) ?? null
|
|
||||||
targetWorkingDir = resolveNewAgentWorkingDir(cwd, checkout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetServerId) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
router.push(buildNewAgentRoute(targetServerId, targetWorkingDir) as any)
|
router.push(buildHostNewAgentRoute(activeServerId) as any)
|
||||||
}, [activeServerId, pathname, selectedAgentId])
|
}, [activeServerId])
|
||||||
|
|
||||||
// Mobile: close sidebar and navigate
|
// Mobile: close sidebar and navigate
|
||||||
const handleCreateAgentCleanMobile = useCallback(() => {
|
const handleCreateAgentCleanMobile = useCallback(() => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { PairLinkModal } from "./pair-link-modal";
|
|||||||
import { NameHostModal } from "./name-host-modal";
|
import { NameHostModal } from "./name-host-modal";
|
||||||
import { resolveAppVersion } from "@/utils/app-version";
|
import { resolveAppVersion } from "@/utils/app-version";
|
||||||
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
||||||
|
import { buildHostRootRoute } from "@/utils/host-routes";
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
@@ -105,7 +106,7 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
|
|||||||
|
|
||||||
const finishOnboarding = useCallback(
|
const finishOnboarding = useCallback(
|
||||||
(serverId: string) => {
|
(serverId: string) => {
|
||||||
router.replace(`/h/${encodeURIComponent(serverId)}` as any);
|
router.replace(buildHostRootRoute(serverId) as any);
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,21 +3,12 @@ import type { TextInput } from "react-native";
|
|||||||
import { router, usePathname, type Href } from "expo-router";
|
import { router, usePathname, type Href } from "expo-router";
|
||||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||||
import { useAggregatedAgents, type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
import { useAggregatedAgents, type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
|
||||||
import {
|
|
||||||
checkoutStatusQueryKey,
|
|
||||||
type CheckoutStatusPayload,
|
|
||||||
} from "@/hooks/use-checkout-status-query";
|
|
||||||
import { queryClient } from "@/query/query-client";
|
|
||||||
import {
|
import {
|
||||||
clearCommandCenterFocusRestoreElement,
|
clearCommandCenterFocusRestoreElement,
|
||||||
takeCommandCenterFocusRestoreElement,
|
takeCommandCenterFocusRestoreElement,
|
||||||
} from "@/utils/command-center-focus-restore";
|
} from "@/utils/command-center-focus-restore";
|
||||||
import {
|
import {
|
||||||
buildNewAgentRoute,
|
buildHostNewAgentRoute,
|
||||||
resolveNewAgentWorkingDir,
|
|
||||||
} from "@/utils/new-agent-routing";
|
|
||||||
import {
|
|
||||||
buildHostWorkspaceAgentTabRoute,
|
buildHostWorkspaceAgentTabRoute,
|
||||||
buildHostSettingsRoute,
|
buildHostSettingsRoute,
|
||||||
parseHostAgentRouteFromPathname,
|
parseHostAgentRouteFromPathname,
|
||||||
@@ -63,12 +54,6 @@ function parseAgentKeyFromPathname(pathname: string): string | null {
|
|||||||
return `${match.serverId}:${match.agentId}`;
|
return `${match.serverId}:${match.agentId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAgentRouteFromPathname(
|
|
||||||
pathname: string
|
|
||||||
): { serverId: string; agentId: string } | null {
|
|
||||||
return parseHostAgentRouteFromPathname(pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandCenterActionDefinition = {
|
type CommandCenterActionDefinition = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -157,24 +142,7 @@ export function useCommandCenter() {
|
|||||||
const newAgentRoute = useMemo<Href>(() => {
|
const newAgentRoute = useMemo<Href>(() => {
|
||||||
const serverIdFromPath =
|
const serverIdFromPath =
|
||||||
parseServerIdFromPathname(pathname) ?? fallbackServerId;
|
parseServerIdFromPathname(pathname) ?? fallbackServerId;
|
||||||
const routeAgent = parseAgentRouteFromPathname(pathname);
|
return serverIdFromPath ? (buildHostNewAgentRoute(serverIdFromPath) as Href) : "/";
|
||||||
if (!routeAgent) {
|
|
||||||
return serverIdFromPath ? (buildNewAgentRoute(serverIdFromPath) as Href) : "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
const { serverId, agentId } = routeAgent;
|
|
||||||
const currentAgent = useSessionStore.getState().sessions[serverId]?.agents?.get(agentId);
|
|
||||||
const cwd = currentAgent?.cwd?.trim();
|
|
||||||
if (!cwd) {
|
|
||||||
return buildNewAgentRoute(serverId) as Href;
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkout =
|
|
||||||
queryClient.getQueryData<CheckoutStatusPayload>(
|
|
||||||
checkoutStatusQueryKey(serverId, cwd)
|
|
||||||
) ?? null;
|
|
||||||
const workingDir = resolveNewAgentWorkingDir(cwd, checkout);
|
|
||||||
return buildNewAgentRoute(serverId, workingDir) as Href;
|
|
||||||
}, [fallbackServerId, pathname]);
|
}, [fallbackServerId, pathname]);
|
||||||
|
|
||||||
const settingsRoute = useMemo<Href>(() => {
|
const settingsRoute = useMemo<Href>(() => {
|
||||||
|
|||||||
@@ -6,16 +6,7 @@ import { useSessionStore } from "@/stores/session-store";
|
|||||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||||
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
|
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
|
||||||
import {
|
import {
|
||||||
checkoutStatusQueryKey,
|
buildHostNewAgentRoute,
|
||||||
type CheckoutStatusPayload,
|
|
||||||
} from "@/hooks/use-checkout-status-query";
|
|
||||||
import { queryClient } from "@/query/query-client";
|
|
||||||
import {
|
|
||||||
buildNewAgentRoute,
|
|
||||||
resolveSelectedAgentForNewAgent,
|
|
||||||
resolveNewAgentWorkingDir,
|
|
||||||
} from "@/utils/new-agent-routing";
|
|
||||||
import {
|
|
||||||
buildHostWorkspaceRoute,
|
buildHostWorkspaceRoute,
|
||||||
parseHostAgentRouteFromPathname,
|
parseHostAgentRouteFromPathname,
|
||||||
parseHostWorkspaceRouteFromPathname,
|
parseHostWorkspaceRouteFromPathname,
|
||||||
@@ -85,26 +76,6 @@ export function useKeyboardShortcuts({
|
|||||||
|
|
||||||
const navigateToNewAgent = (): boolean => {
|
const navigateToNewAgent = (): boolean => {
|
||||||
let targetServerId = parseServerIdFromPathname(pathname);
|
let targetServerId = parseServerIdFromPathname(pathname);
|
||||||
let targetWorkingDir: string | null = null;
|
|
||||||
const selectedAgent = resolveSelectedAgentForNewAgent({
|
|
||||||
pathname,
|
|
||||||
selectedAgentId,
|
|
||||||
});
|
|
||||||
if (selectedAgent) {
|
|
||||||
targetServerId = selectedAgent.serverId;
|
|
||||||
const agent = useSessionStore
|
|
||||||
.getState()
|
|
||||||
.sessions[selectedAgent.serverId]
|
|
||||||
?.agents?.get(selectedAgent.agentId);
|
|
||||||
const cwd = agent?.cwd?.trim();
|
|
||||||
if (cwd) {
|
|
||||||
const checkout =
|
|
||||||
queryClient.getQueryData<CheckoutStatusPayload>(
|
|
||||||
checkoutStatusQueryKey(selectedAgent.serverId, cwd)
|
|
||||||
) ?? null;
|
|
||||||
targetWorkingDir = resolveNewAgentWorkingDir(cwd, checkout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetServerId) {
|
if (!targetServerId) {
|
||||||
const sessionServerIds = Object.keys(useSessionStore.getState().sessions);
|
const sessionServerIds = Object.keys(useSessionStore.getState().sessions);
|
||||||
@@ -115,7 +86,7 @@ export function useKeyboardShortcuts({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push(buildNewAgentRoute(targetServerId, targetWorkingDir) as any);
|
router.push(buildHostNewAgentRoute(targetServerId) as any);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import { shouldClearAgentAttentionOnView } from "@/utils/agent-attention";
|
|||||||
import type { DaemonClient } from "@server/client/daemon-client";
|
import type { DaemonClient } from "@server/client/daemon-client";
|
||||||
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
|
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
|
||||||
import type { ExplorerCheckoutContext } from "@/stores/panel-store";
|
import type { ExplorerCheckoutContext } from "@/stores/panel-store";
|
||||||
|
import { buildHostRootRoute } from "@/utils/host-routes";
|
||||||
|
|
||||||
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
|
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
|
||||||
const IS_DEV = Boolean((globalThis as { __DEV__?: boolean }).__DEV__);
|
const IS_DEV = Boolean((globalThis as { __DEV__?: boolean }).__DEV__);
|
||||||
@@ -528,7 +529,7 @@ function AgentScreenContent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasRedirectedArchivedAgentRef.current = true;
|
hasRedirectedArchivedAgentRef.current = true;
|
||||||
const route: Href = `/h/${encodeURIComponent(serverId)}` as Href;
|
const route: Href = buildHostRootRoute(serverId) as Href;
|
||||||
router.replace(route);
|
router.replace(route);
|
||||||
}, [agent?.archivedAt, resolvedAgentId, router, serverId]);
|
}, [agent?.archivedAt, resolvedAgentId, router, serverId]);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { BackHeader } from "@/components/headers/back-header";
|
|||||||
import { AgentList } from "@/components/agent-list";
|
import { AgentList } from "@/components/agent-list";
|
||||||
import { useAllAgentsList } from "@/hooks/use-all-agents-list";
|
import { useAllAgentsList } from "@/hooks/use-all-agents-list";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
|
import { buildHostRootRoute } from "@/utils/host-routes";
|
||||||
|
|
||||||
export function AgentsScreen({ serverId }: { serverId: string }) {
|
export function AgentsScreen({ serverId }: { serverId: string }) {
|
||||||
const { agents, isRevalidating, refreshAll } = useAllAgentsList({
|
const { agents, isRevalidating, refreshAll } = useAllAgentsList({
|
||||||
@@ -43,7 +44,7 @@ export function AgentsScreen({ serverId }: { serverId: string }) {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<BackHeader
|
<BackHeader
|
||||||
title="All agents"
|
title="All agents"
|
||||||
onBack={() => router.replace(`/h/${encodeURIComponent(serverId)}` as any)}
|
onBack={() => router.replace(buildHostRootRoute(serverId) as any)}
|
||||||
/>
|
/>
|
||||||
<AgentList
|
<AgentList
|
||||||
agents={sortedAgents}
|
agents={sortedAgents}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { useCallback, useEffect, useMemo, useReducer } from "react";
|
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
||||||
import { Keyboard, Platform, ScrollView, Text, View } from "react-native";
|
import { Keyboard, Platform, ScrollView, Text, View } from "react-native";
|
||||||
import { StyleSheet } from "react-native-unistyles";
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
import { AgentInputArea } from "@/components/agent-input-area";
|
import { AgentInputArea } from "@/components/agent-input-area";
|
||||||
import { AgentConfigRow } from "@/components/agent-form/agent-form-dropdowns";
|
import { AgentConfigRow } from "@/components/agent-form/agent-form-dropdowns";
|
||||||
|
import { FileDropZone } from "@/components/file-drop-zone";
|
||||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||||
|
import type { ImageAttachment } from "@/components/message-input";
|
||||||
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
||||||
import { useAgentFormState } from "@/hooks/use-agent-form-state";
|
import { useAgentFormState } from "@/hooks/use-agent-form-state";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
@@ -63,6 +65,7 @@ export function WorkspaceDraftAgentTab({
|
|||||||
onCreated,
|
onCreated,
|
||||||
}: WorkspaceDraftAgentTabProps) {
|
}: WorkspaceDraftAgentTabProps) {
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
|
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||||
|
|
||||||
const setPendingCreateAttempt = useCreateFlowStore((state) => state.setPending);
|
const setPendingCreateAttempt = useCreateFlowStore((state) => state.setPending);
|
||||||
const updatePendingAgentId = useCreateFlowStore((state) => state.updateAgentId);
|
const updatePendingAgentId = useCreateFlowStore((state) => state.updateAgentId);
|
||||||
@@ -322,64 +325,75 @@ export function WorkspaceDraftAgentTab({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleFilesDropped = useCallback((files: ImageAttachment[]) => {
|
||||||
|
addImagesRef.current?.(files);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddImagesCallback = useCallback((addImages: (images: ImageAttachment[]) => void) => {
|
||||||
|
addImagesRef.current = addImages;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<FileDropZone onFilesDropped={handleFilesDropped}>
|
||||||
<View style={styles.contentContainer}>
|
<View style={styles.container}>
|
||||||
{machine.tag === "creating" && draftAgent ? (
|
<View style={styles.contentContainer}>
|
||||||
<View style={styles.streamContainer}>
|
{machine.tag === "creating" && draftAgent ? (
|
||||||
<AgentStreamView
|
<View style={styles.streamContainer}>
|
||||||
agentId={tabId}
|
<AgentStreamView
|
||||||
serverId={serverId}
|
agentId={tabId}
|
||||||
agent={draftAgent}
|
serverId={serverId}
|
||||||
streamItems={optimisticStreamItems}
|
agent={draftAgent}
|
||||||
pendingPermissions={EMPTY_PENDING_PERMISSIONS}
|
streamItems={optimisticStreamItems}
|
||||||
/>
|
pendingPermissions={EMPTY_PENDING_PERMISSIONS}
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<ScrollView style={styles.scrollView} contentContainerStyle={styles.configScrollContent}>
|
|
||||||
<View style={styles.configSection}>
|
|
||||||
<AgentConfigRow
|
|
||||||
providerDefinitions={providerDefinitions}
|
|
||||||
selectedProvider={selectedProvider}
|
|
||||||
onSelectProvider={setProviderFromUser}
|
|
||||||
modeOptions={modeOptions}
|
|
||||||
selectedMode={selectedMode}
|
|
||||||
onSelectMode={setModeFromUser}
|
|
||||||
models={availableModels}
|
|
||||||
selectedModel={selectedModel}
|
|
||||||
onSelectModel={setModelFromUser}
|
|
||||||
isModelLoading={isModelLoading}
|
|
||||||
thinkingOptions={availableThinkingOptions}
|
|
||||||
selectedThinkingOptionId={selectedThinkingOptionId}
|
|
||||||
onSelectThinkingOption={setThinkingOptionFromUser}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{formErrorMessage ? (
|
|
||||||
<View style={styles.errorContainer}>
|
|
||||||
<Text style={styles.errorText}>{formErrorMessage}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
) : (
|
||||||
)}
|
<ScrollView style={styles.scrollView} contentContainerStyle={styles.configScrollContent}>
|
||||||
</View>
|
<View style={styles.configSection}>
|
||||||
|
<AgentConfigRow
|
||||||
|
providerDefinitions={providerDefinitions}
|
||||||
|
selectedProvider={selectedProvider}
|
||||||
|
onSelectProvider={setProviderFromUser}
|
||||||
|
modeOptions={modeOptions}
|
||||||
|
selectedMode={selectedMode}
|
||||||
|
onSelectMode={setModeFromUser}
|
||||||
|
models={availableModels}
|
||||||
|
selectedModel={selectedModel}
|
||||||
|
onSelectModel={setModelFromUser}
|
||||||
|
isModelLoading={isModelLoading}
|
||||||
|
thinkingOptions={availableThinkingOptions}
|
||||||
|
selectedThinkingOptionId={selectedThinkingOptionId}
|
||||||
|
onSelectThinkingOption={setThinkingOptionFromUser}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
|
||||||
<View style={styles.inputAreaWrapper}>
|
{formErrorMessage ? (
|
||||||
<AgentInputArea
|
<View style={styles.errorContainer}>
|
||||||
agentId={tabId}
|
<Text style={styles.errorText}>{formErrorMessage}</Text>
|
||||||
serverId={serverId}
|
</View>
|
||||||
onSubmitMessage={handleCreateFromInput}
|
) : null}
|
||||||
isSubmitLoading={isSubmitting}
|
</View>
|
||||||
blurOnSubmit={true}
|
</ScrollView>
|
||||||
value={promptValue}
|
)}
|
||||||
onChangeText={(next) => dispatch({ type: "DRAFT_SET_PROMPT", text: next })}
|
</View>
|
||||||
autoFocus={machine.tag === "draft"}
|
|
||||||
commandDraftConfig={draftCommandConfig}
|
<View style={styles.inputAreaWrapper}>
|
||||||
draftId={draftId}
|
<AgentInputArea
|
||||||
/>
|
agentId={tabId}
|
||||||
|
serverId={serverId}
|
||||||
|
onSubmitMessage={handleCreateFromInput}
|
||||||
|
isSubmitLoading={isSubmitting}
|
||||||
|
blurOnSubmit={true}
|
||||||
|
value={promptValue}
|
||||||
|
onChangeText={(next) => dispatch({ type: "DRAFT_SET_PROMPT", text: next })}
|
||||||
|
autoFocus={machine.tag === "draft"}
|
||||||
|
onAddImages={handleAddImagesCallback}
|
||||||
|
commandDraftConfig={draftCommandConfig}
|
||||||
|
draftId={draftId}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</FileDropZone>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
buildHostNewAgentRoute,
|
||||||
|
buildHostRootRoute,
|
||||||
buildHostWorkspaceAgentTabRoute,
|
buildHostWorkspaceAgentTabRoute,
|
||||||
buildHostWorkspaceFileTabRoute,
|
buildHostWorkspaceFileTabRoute,
|
||||||
buildHostWorkspaceRoute,
|
buildHostWorkspaceRoute,
|
||||||
@@ -70,6 +72,14 @@ describe("workspace route parsing", () => {
|
|||||||
expect(buildHostWorkspaceRoute("local", "/tmp/repo")).toBe("/h/local/workspace/L3RtcC9yZXBv");
|
expect(buildHostWorkspaceRoute("local", "/tmp/repo")).toBe("/h/local/workspace/L3RtcC9yZXBv");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds host root routes", () => {
|
||||||
|
expect(buildHostRootRoute("local")).toBe("/h/local");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds host new-agent routes", () => {
|
||||||
|
expect(buildHostNewAgentRoute("local")).toBe("/h/local/new-agent");
|
||||||
|
});
|
||||||
|
|
||||||
it("builds workspace agent tab routes", () => {
|
it("builds workspace agent tab routes", () => {
|
||||||
expect(buildHostWorkspaceAgentTabRoute("local", "/tmp/repo", "agent-1")).toBe(
|
expect(buildHostWorkspaceAgentTabRoute("local", "/tmp/repo", "agent-1")).toBe(
|
||||||
"/h/local/workspace/L3RtcC9yZXBv/tab/agent_agent-1"
|
"/h/local/workspace/L3RtcC9yZXBv/tab/agent_agent-1"
|
||||||
|
|||||||
@@ -359,25 +359,41 @@ export function buildHostAgentDetailRoute(
|
|||||||
if (!normalizedServerId || !normalizedAgentId) {
|
if (!normalizedServerId || !normalizedAgentId) {
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
return `/h/${encodeSegment(normalizedServerId)}/agent/${encodeSegment(
|
return `${buildHostRootRoute(normalizedServerId)}/agent/${encodeSegment(
|
||||||
normalizedAgentId
|
normalizedAgentId
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildHostAgentsRoute(serverId: string): string {
|
export function buildHostRootRoute(serverId: string): string {
|
||||||
const normalized = trimNonEmpty(serverId);
|
const normalized = trimNonEmpty(serverId);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
return `/h/${encodeSegment(normalized)}/agents`;
|
return `/h/${encodeSegment(normalized)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildHostAgentsRoute(serverId: string): string {
|
||||||
|
const base = buildHostRootRoute(serverId);
|
||||||
|
if (base === "/") {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
return `${base}/agents`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildHostNewAgentRoute(serverId: string): string {
|
||||||
|
const base = buildHostRootRoute(serverId);
|
||||||
|
if (base === "/") {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
return `${base}/new-agent`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildHostSettingsRoute(serverId: string): string {
|
export function buildHostSettingsRoute(serverId: string): string {
|
||||||
const normalized = trimNonEmpty(serverId);
|
const base = buildHostRootRoute(serverId);
|
||||||
if (!normalized) {
|
if (base === "/") {
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
return `/h/${encodeSegment(normalized)}/settings`;
|
return `${base}/settings`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapPathnameToServer(
|
export function mapPathnameToServer(
|
||||||
@@ -390,13 +406,16 @@ export function mapPathnameToServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const suffix = pathname.replace(/^\/h\/[^/]+\/?/, "");
|
const suffix = pathname.replace(/^\/h\/[^/]+\/?/, "");
|
||||||
const base = `/h/${encodeSegment(normalized)}`;
|
const base = buildHostRootRoute(normalized);
|
||||||
if (suffix.startsWith("settings")) {
|
if (suffix.startsWith("settings")) {
|
||||||
return `${base}/settings`;
|
return `${base}/settings`;
|
||||||
}
|
}
|
||||||
if (suffix.startsWith("agents")) {
|
if (suffix.startsWith("agents")) {
|
||||||
return `${base}/agents`;
|
return `${base}/agents`;
|
||||||
}
|
}
|
||||||
|
if (suffix.startsWith("new-agent")) {
|
||||||
|
return `${base}/new-agent`;
|
||||||
|
}
|
||||||
if (suffix.startsWith("workspace/")) {
|
if (suffix.startsWith("workspace/")) {
|
||||||
return `${base}/${suffix}`;
|
return `${base}/${suffix}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
buildHostAgentDetailRoute,
|
buildHostAgentDetailRoute,
|
||||||
|
buildHostRootRoute,
|
||||||
buildHostWorkspaceAgentTabRoute,
|
buildHostWorkspaceAgentTabRoute,
|
||||||
} from "@/utils/host-routes";
|
} from "@/utils/host-routes";
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ export function buildNotificationRoute(data: NotificationData): string {
|
|||||||
return buildHostAgentDetailRoute(serverId, agentId);
|
return buildHostAgentDetailRoute(serverId, agentId);
|
||||||
}
|
}
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
return `/h/${encodeURIComponent(serverId)}`;
|
return buildHostRootRoute(serverId);
|
||||||
}
|
}
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import type { Logger } from "pino";
|
import type { Logger } from "pino";
|
||||||
|
import { exec } from "node:child_process";
|
||||||
|
import { promisify } from "node:util";
|
||||||
|
import { sep } from "node:path";
|
||||||
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
||||||
import type { TerminalSession } from "../terminal/terminal.js";
|
import type { TerminalSession } from "../terminal/terminal.js";
|
||||||
import {
|
import {
|
||||||
createWorktree,
|
createWorktree,
|
||||||
getWorktreeTerminalSpecs,
|
getWorktreeTerminalSpecs,
|
||||||
|
listPaseoWorktrees,
|
||||||
resolveWorktreeRuntimeEnv,
|
resolveWorktreeRuntimeEnv,
|
||||||
runWorktreeSetupCommands,
|
runWorktreeSetupCommands,
|
||||||
WorktreeSetupError,
|
WorktreeSetupError,
|
||||||
@@ -42,6 +46,12 @@ export interface CreateAgentWorktreeOptions {
|
|||||||
const MAX_WORKTREE_SETUP_COMMAND_OUTPUT_BYTES = 64 * 1024;
|
const MAX_WORKTREE_SETUP_COMMAND_OUTPUT_BYTES = 64 * 1024;
|
||||||
const WORKTREE_SETUP_TRUNCATION_MARKER = "\n...<output truncated in the middle>...\n";
|
const WORKTREE_SETUP_TRUNCATION_MARKER = "\n...<output truncated in the middle>...\n";
|
||||||
const WORKTREE_BOOTSTRAP_TERMINAL_READY_TIMEOUT_MS = 1_500;
|
const WORKTREE_BOOTSTRAP_TERMINAL_READY_TIMEOUT_MS = 1_500;
|
||||||
|
const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
|
||||||
|
...process.env,
|
||||||
|
GIT_OPTIONAL_LOCKS: "0",
|
||||||
|
};
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
const worktreeSetupEligibility = new WeakMap<WorktreeConfig, boolean>();
|
||||||
|
|
||||||
type MiddleTruncationAccumulator = {
|
type MiddleTruncationAccumulator = {
|
||||||
totalBytes: number;
|
totalBytes: number;
|
||||||
@@ -153,7 +163,18 @@ function renderMiddleTruncationAccumulator(
|
|||||||
export async function createAgentWorktree(
|
export async function createAgentWorktree(
|
||||||
options: CreateAgentWorktreeOptions
|
options: CreateAgentWorktreeOptions
|
||||||
): Promise<WorktreeConfig> {
|
): Promise<WorktreeConfig> {
|
||||||
return createWorktree({
|
const existingWorktree = await findExistingPaseoWorktreeBySlug(options);
|
||||||
|
if (existingWorktree) {
|
||||||
|
const branchName = await resolveBranchNameForWorktreePath(existingWorktree.path);
|
||||||
|
const reusedWorktree = {
|
||||||
|
branchName,
|
||||||
|
worktreePath: existingWorktree.path,
|
||||||
|
};
|
||||||
|
worktreeSetupEligibility.set(reusedWorktree, false);
|
||||||
|
return reusedWorktree;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdWorktree = await createWorktree({
|
||||||
branchName: options.branchName,
|
branchName: options.branchName,
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
baseBranch: options.baseBranch,
|
baseBranch: options.baseBranch,
|
||||||
@@ -161,6 +182,29 @@ export async function createAgentWorktree(
|
|||||||
runSetup: false,
|
runSetup: false,
|
||||||
paseoHome: options.paseoHome,
|
paseoHome: options.paseoHome,
|
||||||
});
|
});
|
||||||
|
worktreeSetupEligibility.set(createdWorktree, true);
|
||||||
|
return createdWorktree;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findExistingPaseoWorktreeBySlug(options: CreateAgentWorktreeOptions) {
|
||||||
|
const worktrees = await listPaseoWorktrees({
|
||||||
|
cwd: options.cwd,
|
||||||
|
paseoHome: options.paseoHome,
|
||||||
|
});
|
||||||
|
const slugSuffix = `${sep}${options.worktreeSlug}`;
|
||||||
|
return worktrees.find((worktree) => worktree.path.endsWith(slugSuffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveBranchNameForWorktreePath(worktreePath: string): Promise<string> {
|
||||||
|
const { stdout } = await execAsync("git branch --show-current", {
|
||||||
|
cwd: worktreePath,
|
||||||
|
env: READ_ONLY_GIT_ENV,
|
||||||
|
});
|
||||||
|
const branchName = stdout.trim();
|
||||||
|
if (!branchName) {
|
||||||
|
throw new Error(`Unable to resolve branch for existing worktree: ${worktreePath}`);
|
||||||
|
}
|
||||||
|
return branchName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDurationMs(durationMs: number): string {
|
function formatDurationMs(durationMs: number): string {
|
||||||
@@ -467,6 +511,10 @@ async function runWorktreeTerminalBootstrap(
|
|||||||
export async function runAsyncWorktreeBootstrap(
|
export async function runAsyncWorktreeBootstrap(
|
||||||
options: RunAsyncWorktreeBootstrapOptions
|
options: RunAsyncWorktreeBootstrapOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (worktreeSetupEligibility.get(options.worktree) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const setupCallId = uuidv4();
|
const setupCallId = uuidv4();
|
||||||
let setupResults: WorktreeSetupCommandResult[] = [];
|
let setupResults: WorktreeSetupCommandResult[] = [];
|
||||||
let runtimeEnv: WorktreeRuntimeEnv | null = null;
|
let runtimeEnv: WorktreeRuntimeEnv | null = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user