diff --git a/src/app/folder/layout.tsx b/src/app/folder/layout.tsx index a1bbd7d..b19a759 100644 --- a/src/app/folder/layout.tsx +++ b/src/app/folder/layout.tsx @@ -1,6 +1,13 @@ "use client" -import { Suspense, useCallback, useEffect, useRef, useState } from "react" +import { + Suspense, + useMemo, + useCallback, + useEffect, + useRef, + useState, +} from "react" import { useSearchParams } from "next/navigation" import type { ImperativePanelGroupHandle } from "react-resizable-panels" import { FolderTitleBar } from "@/components/layout/folder-title-bar" @@ -9,9 +16,12 @@ import { StatusBar } from "@/components/layout/status-bar" import { FolderProvider } from "@/contexts/folder-context" import { TaskProvider } from "@/contexts/task-context" import { AlertProvider } from "@/contexts/alert-context" -import { AcpConnectionsProvider } from "@/contexts/acp-connections-context" +import { + AcpConnectionsProvider, + useAcpActions, +} from "@/contexts/acp-connections-context" import { ConversationRuntimeProvider } from "@/contexts/conversation-runtime-context" -import { TabProvider } from "@/contexts/tab-context" +import { TabProvider, useTabContext } from "@/contexts/tab-context" import { SessionStatsProvider } from "@/contexts/session-stats-context" import { SidebarProvider, useSidebarContext } from "@/contexts/sidebar-context" import { @@ -58,6 +68,17 @@ const MIN_CENTER_WIDTH_PX = 420 const MIN_WORKSPACE_HEIGHT_PX = 220 const LAYOUT_EPSILON = 0.25 +/** Syncs open tab keys from TabProvider to AcpConnectionsProvider */ +function TabKeysSync() { + const { tabs } = useTabContext() + const { registerOpenTabKeys } = useAcpActions() + const keys = useMemo(() => new Set(tabs.map((t) => t.id)), [tabs]) + useEffect(() => { + registerOpenTabKeys(keys) + }, [keys, registerOpenTabKeys]) + return null +} + function isSameLayout(a: number[], b: number[]): boolean { if (a.length !== b.length) return false return a.every((value, index) => Math.abs(value - b[index]) <= LAYOUT_EPSILON) @@ -649,6 +670,7 @@ function FolderLayoutInner({ children }: { children: React.ReactNode }) { + setActiveKey(key: string | null): void touchActivity(contextKey: string): void + registerOpenTabKeys(keys: Set): void } const AcpActionsContext = createContext(null) @@ -1147,6 +1148,9 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { // connectionId → contextKey reverse mapping const reverseMapRef = useRef(new Map()) + // Open tab keys — updated by child TabProvider via registerOpenTabKeys + const openTabKeysRef = useRef(new Set()) + // Guard against concurrent connect() calls const connectingKeysRef = useRef(new Set()) // Keys whose disconnect was requested while connect was still in flight @@ -1309,6 +1313,10 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { lastActivityRef.current.set(contextKey, Date.now()) }, []) + const registerOpenTabKeys = useCallback((keys: Set) => { + openTabKeysRef.current = keys + }, []) + const flushStreamingQueue = useCallback(() => { flushTimerRef.current = null const queued = streamingQueueRef.current @@ -1649,9 +1657,11 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { const now = Date.now() const currentActiveKey = storeRef.current.activeKey + const currentOpenTabKeys = openTabKeysRef.current const toDisconnect: { contextKey: string; connectionId: string }[] = [] for (const [contextKey, conn] of storeRef.current.connections) { if (contextKey === currentActiveKey) continue + if (currentOpenTabKeys.has(contextKey)) continue if ( conn.status === "prompting" || conn.status === "connecting" || @@ -1923,6 +1933,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { respondPermission, setActiveKey, touchActivity, + registerOpenTabKeys, }), [ connect, @@ -1935,6 +1946,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { respondPermission, setActiveKey, touchActivity, + registerOpenTabKeys, ] )