fix: cross-directory session visibility and API routing
fix: remove roots filter from session list to show all sessions
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useCallback, useState, useRef } from "react"
|
import { useCallback, useState, useRef } from "react"
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -14,10 +14,11 @@ import {
|
|||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
} from "react-native"
|
} from "react-native"
|
||||||
import { router } from "expo-router"
|
import { router, useFocusEffect } from "expo-router"
|
||||||
import { Ionicons } from "@expo/vector-icons"
|
import { Ionicons } from "@expo/vector-icons"
|
||||||
import { useSessions } from "../../src/stores/sessions"
|
import { useSessions } from "../../src/stores/sessions"
|
||||||
import { useConnections } from "../../src/stores/connections"
|
import { useConnections } from "../../src/stores/connections"
|
||||||
|
import { useCatalog } from "../../src/stores/catalog"
|
||||||
import type BottomSheet from "@gorhom/bottom-sheet"
|
import type BottomSheet from "@gorhom/bottom-sheet"
|
||||||
import type { Session } from "../../src/lib/sdk"
|
import type { Session } from "../../src/lib/sdk"
|
||||||
import { DirectorySwitcher } from "../../src/components/chat"
|
import { DirectorySwitcher } from "../../src/components/chat"
|
||||||
@@ -123,6 +124,7 @@ export default function SessionsScreen() {
|
|||||||
addRecentDirectory,
|
addRecentDirectory,
|
||||||
recentDirectories,
|
recentDirectories,
|
||||||
} = useConnections()
|
} = useConnections()
|
||||||
|
const loadCatalog = useCatalog((s) => s.load)
|
||||||
const dirSheetRef = useRef<BottomSheet>(null)
|
const dirSheetRef = useRef<BottomSheet>(null)
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
|
|
||||||
@@ -131,16 +133,19 @@ export default function SessionsScreen() {
|
|||||||
await switchDirectory(dir)
|
await switchDirectory(dir)
|
||||||
loadSessions()
|
loadSessions()
|
||||||
refreshProject()
|
refreshProject()
|
||||||
|
loadCatalog()
|
||||||
},
|
},
|
||||||
[switchDirectory, loadSessions, refreshProject],
|
[switchDirectory, loadSessions, refreshProject, loadCatalog],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
if (client) {
|
if (client) {
|
||||||
loadSessions()
|
loadSessions()
|
||||||
refreshProject()
|
refreshProject()
|
||||||
}
|
}
|
||||||
}, [client])
|
}, [client, loadSessions, refreshProject]),
|
||||||
|
)
|
||||||
|
|
||||||
const onRefresh = useCallback(async () => {
|
const onRefresh = useCallback(async () => {
|
||||||
setRefreshing(true)
|
setRefreshing(true)
|
||||||
@@ -155,12 +160,14 @@ export default function SessionsScreen() {
|
|||||||
|
|
||||||
const submitRename = useCallback(async () => {
|
const submitRename = useCallback(async () => {
|
||||||
const title = renameText.trim()
|
const title = renameText.trim()
|
||||||
if (!title || !renaming || !client) return
|
if (!title || !renaming) return
|
||||||
await client.session.update(renaming.id, { title })
|
const renameClient = renaming.directory ? (clientForDirectory(renaming.directory) ?? client) : client
|
||||||
|
if (!renameClient) return
|
||||||
|
await renameClient.session.update(renaming.id, { title })
|
||||||
setRenaming(null)
|
setRenaming(null)
|
||||||
setRenameText("")
|
setRenameText("")
|
||||||
loadSessions()
|
loadSessions()
|
||||||
}, [renaming, renameText, client, loadSessions])
|
}, [renaming, renameText, client, clientForDirectory, loadSessions])
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
(session: Session) => {
|
(session: Session) => {
|
||||||
|
|||||||
@@ -107,7 +107,13 @@ export default function SessionScreen() {
|
|||||||
const isSending = useSessions((s) => !!(currentSession && s.sending[currentSession.id]))
|
const isSending = useSessions((s) => !!(currentSession && s.sending[currentSession.id]))
|
||||||
|
|
||||||
const { authenticateForMessage } = useAuth()
|
const { authenticateForMessage } = useAuth()
|
||||||
const { client } = useConnections()
|
const { client, clientForDirectory } = useConnections()
|
||||||
|
|
||||||
|
// Use directory-aware client for sessions that belong to a project other than the active one
|
||||||
|
const sessionClient = useMemo(
|
||||||
|
() => (currentSession?.directory ? (clientForDirectory(currentSession.directory) ?? client) : client),
|
||||||
|
[currentSession?.directory, clientForDirectory, client],
|
||||||
|
)
|
||||||
|
|
||||||
// Catalog
|
// Catalog
|
||||||
const catalog = useCatalog()
|
const catalog = useCatalog()
|
||||||
@@ -170,9 +176,11 @@ export default function SessionScreen() {
|
|||||||
selectSession(id, directory).then(() => {
|
selectSession(id, directory).then(() => {
|
||||||
// Re-fetch pending permissions/questions from the server to recover from
|
// Re-fetch pending permissions/questions from the server to recover from
|
||||||
// missed SSE events or failed optimistic removals
|
// missed SSE events or failed optimistic removals
|
||||||
if (client) refreshPending(client, id)
|
const connState = useConnections.getState()
|
||||||
|
const c = directory ? (connState.clientForDirectory(directory) ?? connState.client) : connState.client
|
||||||
|
if (c) refreshPending(c, id)
|
||||||
})
|
})
|
||||||
}, [id])
|
}, [id, directory])
|
||||||
|
|
||||||
// Sync model chip from latest assistant message
|
// Sync model chip from latest assistant message
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -320,8 +328,8 @@ export default function SessionScreen() {
|
|||||||
const [cmdName, ...args] = text.split(" ")
|
const [cmdName, ...args] = text.split(" ")
|
||||||
const name = cmdName.slice(1)
|
const name = cmdName.slice(1)
|
||||||
const match = serverCommands.find((c) => c.name === name)
|
const match = serverCommands.find((c) => c.name === name)
|
||||||
if (match && client && currentSession) {
|
if (match && sessionClient && currentSession) {
|
||||||
client.session
|
sessionClient.session
|
||||||
.command(currentSession.id, {
|
.command(currentSession.id, {
|
||||||
command: name,
|
command: name,
|
||||||
arguments: args.join(" "),
|
arguments: args.join(" "),
|
||||||
@@ -359,7 +367,7 @@ export default function SessionScreen() {
|
|||||||
}, [loadingMore])
|
}, [loadingMore])
|
||||||
|
|
||||||
const handlePermissionReply = async (requestID: string, reply: "once" | "always" | "reject") => {
|
const handlePermissionReply = async (requestID: string, reply: "once" | "always" | "reject") => {
|
||||||
if (!client || !sessionID) return
|
if (!sessionClient || !sessionID) return
|
||||||
// Snapshot for rollback
|
// Snapshot for rollback
|
||||||
const snapshot = useEvents.getState().permissions[sessionID] || []
|
const snapshot = useEvents.getState().permissions[sessionID] || []
|
||||||
// Optimistically remove from UI
|
// Optimistically remove from UI
|
||||||
@@ -370,7 +378,7 @@ export default function SessionScreen() {
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
try {
|
try {
|
||||||
await client.permission.reply(requestID, reply)
|
await sessionClient.permission.reply(requestID, reply)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Permission reply failed:", err)
|
console.error("Permission reply failed:", err)
|
||||||
// Restore the prompt so the user can retry
|
// Restore the prompt so the user can retry
|
||||||
@@ -382,7 +390,7 @@ export default function SessionScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleQuestionReply = async (requestID: string, answers: string[][]) => {
|
const handleQuestionReply = async (requestID: string, answers: string[][]) => {
|
||||||
if (!client || !sessionID) return
|
if (!sessionClient || !sessionID) return
|
||||||
const snapshot = useEvents.getState().questions[sessionID] || []
|
const snapshot = useEvents.getState().questions[sessionID] || []
|
||||||
useEvents.setState((state) => ({
|
useEvents.setState((state) => ({
|
||||||
questions: {
|
questions: {
|
||||||
@@ -391,7 +399,7 @@ export default function SessionScreen() {
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
try {
|
try {
|
||||||
await client.question.reply(requestID, answers)
|
await sessionClient.question.reply(requestID, answers)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Question reply failed:", err)
|
console.error("Question reply failed:", err)
|
||||||
useEvents.setState((state) => ({
|
useEvents.setState((state) => ({
|
||||||
@@ -402,7 +410,7 @@ export default function SessionScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleQuestionReject = async (requestID: string) => {
|
const handleQuestionReject = async (requestID: string) => {
|
||||||
if (!client || !sessionID) return
|
if (!sessionClient || !sessionID) return
|
||||||
const snapshot = useEvents.getState().questions[sessionID] || []
|
const snapshot = useEvents.getState().questions[sessionID] || []
|
||||||
useEvents.setState((state) => ({
|
useEvents.setState((state) => ({
|
||||||
questions: {
|
questions: {
|
||||||
@@ -411,7 +419,7 @@ export default function SessionScreen() {
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
try {
|
try {
|
||||||
await client.question.reject(requestID)
|
await sessionClient.question.reject(requestID)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Question reject failed:", err)
|
console.error("Question reject failed:", err)
|
||||||
useEvents.setState((state) => ({
|
useEvents.setState((state) => ({
|
||||||
|
|||||||
@@ -85,16 +85,6 @@ export const useCatalog = create<CatalogState>((set, get) => ({
|
|||||||
.filter((p) => p.models.length > 0)
|
.filter((p) => p.models.length > 0)
|
||||||
: []
|
: []
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[catalog] loaded:",
|
|
||||||
agents.length,
|
|
||||||
"agents,",
|
|
||||||
commands.length,
|
|
||||||
"commands,",
|
|
||||||
providers.length,
|
|
||||||
"providers (" + providers.reduce((n, p) => n + p.models.length, 0) + " models)",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter out hidden agents
|
// Filter out hidden agents
|
||||||
const visible = agents.filter((a) => !a.hidden)
|
const visible = agents.filter((a) => !a.hidden)
|
||||||
|
|
||||||
|
|||||||
@@ -73,15 +73,22 @@ export const useSessions = create<SessionsState>((set, get) => ({
|
|||||||
error: null,
|
error: null,
|
||||||
|
|
||||||
loadSessions: async () => {
|
loadSessions: async () => {
|
||||||
const client = useConnections.getState().client
|
const connState = useConnections.getState()
|
||||||
if (!client) {
|
if (!connState.client) {
|
||||||
set({ error: "No active connection" })
|
set({ error: "No active connection" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When no project directory is explicitly selected, use the server home path so the
|
||||||
|
// session list shows all recent sessions across all projects — not just the server's CWD.
|
||||||
|
const listClient =
|
||||||
|
!connState.activeConnection?.directory && connState.serverHome
|
||||||
|
? connState.clientForDirectory(connState.serverHome)
|
||||||
|
: connState.client
|
||||||
|
|
||||||
try {
|
try {
|
||||||
set({ isLoading: true, error: null })
|
set({ isLoading: true, error: null })
|
||||||
const sessions = await client.session.list({ roots: true, limit: 50 })
|
const sessions = await (listClient || connState.client).session.list({ roots: true, limit: 50 })
|
||||||
set({ sessions, isLoading: false })
|
set({ sessions, isLoading: false })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
set({ error: "Failed to load sessions", isLoading: false })
|
set({ error: "Failed to load sessions", isLoading: false })
|
||||||
@@ -186,7 +193,8 @@ export const useSessions = create<SessionsState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
deleteSession: async (sessionID) => {
|
deleteSession: async (sessionID) => {
|
||||||
const client = useConnections.getState().client
|
const session = get().sessions.find((s) => s.id === sessionID)
|
||||||
|
const client = clientFor(session?.directory)
|
||||||
if (!client) {
|
if (!client) {
|
||||||
set({ error: "No active connection" })
|
set({ error: "No active connection" })
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user