diff --git a/packages/ui/src/stores/permissionStore.ts b/packages/ui/src/stores/permissionStore.ts index 756bc598..b4444855 100644 --- a/packages/ui/src/stores/permissionStore.ts +++ b/packages/ui/src/stores/permissionStore.ts @@ -107,14 +107,13 @@ const normalizeDirectoryCandidate = (value: unknown): string | null => { return trimmed.length > 0 ? trimmed : null; }; -const collectPendingFromSyncStores = (sessionScope: Set): Array<{ id: string; sessionID: string }> => { +const collectPendingFromSyncStores = (): Array<{ id: string; sessionID: string }> => { try { const stores = getSyncChildStores(); const pending: Array<{ id: string; sessionID: string }> = []; for (const store of stores.children.values()) { const permissionMap = store.getState().permission ?? {}; for (const [sessionId, entries] of Object.entries(permissionMap)) { - if (!sessionScope.has(sessionId)) continue; for (const permission of entries ?? []) { if (!permission?.id) continue; pending.push({ id: permission.id, sessionID: permission.sessionID || sessionId }); @@ -127,6 +126,68 @@ const collectPendingFromSyncStores = (sessionScope: Set): Array<{ id: st } }; +const sessionBelongsToScope = async ( + sessionID: string, + rootSessionID: string, + knownSessions: Session[], + directories: string[], +): Promise => { + if (sessionID === rootSessionID) { + return true; + } + + const knownById = new Map(); + for (const session of knownSessions) { + knownById.set(session.id, session); + } + + const fetchedById = new Map(); + const fetchSession = async (id: string): Promise => { + const known = knownById.get(id) ?? fetchedById.get(id); + if (known) return known; + + for (const directory of directories) { + try { + const result = await opencodeClient.getScopedSdkClient(directory).session.get({ + sessionID: id, + directory, + }); + if (result.data) { + fetchedById.set(id, result.data); + return result.data; + } + } catch { + // Try the next known project directory. + } + } + + try { + const result = await opencodeClient.getSdkClient().session.get({ sessionID: id }); + if (result.data) { + fetchedById.set(id, result.data); + return result.data; + } + } catch { + // Missing session metadata means we cannot safely inherit the parent setting. + } + + return null; + }; + + const seen = new Set(); + let current: string | undefined = sessionID; + while (current && !seen.has(current)) { + if (current === rootSessionID) { + return true; + } + seen.add(current); + const session = await fetchSession(current); + current = session?.parentID ?? undefined; + } + + return false; +}; + const autoRespondsPermissionBySession = ( autoAccept: PermissionAutoAcceptMap, sessions: Session[], @@ -204,19 +265,29 @@ export const usePermissionStore = create()( } } - const pendingFromStores = collectPendingFromSyncStores(sessionScope); + const directoryList = Array.from(directories); + const pendingFromStores = collectPendingFromSyncStores(); const pendingFromApi = await opencodeClient.listPendingPermissions({ directories: Array.from(directories) }); const mergedPending = new Map(); for (const permission of pendingFromStores) { - mergedPending.set(permission.id, permission); + if (sessionScope.has(permission.sessionID)) { + mergedPending.set(permission.id, permission); + continue; + } + if (await sessionBelongsToScope(permission.sessionID, sessionId, sessions, directoryList)) { + mergedPending.set(permission.id, permission); + } } for (const permission of pendingFromApi) { if (!permission?.id || !permission?.sessionID) { continue; } if (!sessionScope.has(permission.sessionID)) { - continue; + const belongsToScope = await sessionBelongsToScope(permission.sessionID, sessionId, sessions, directoryList); + if (!belongsToScope) { + continue; + } } mergedPending.set(permission.id, { id: permission.id, sessionID: permission.sessionID }); } diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index 461a3fe7..46f8a89b 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -67,6 +67,7 @@ function routeMessage(params: { providerID: string modelID: string agent?: string + agentMentionName?: string variant?: string inputMode?: "normal" | "shell" files?: Array<{ type: "file"; mime: string; url: string; filename: string }> @@ -133,6 +134,7 @@ function routeMessage(params: { modelID: params.modelID, text: params.content, agent: params.agent, + agentMentions: params.agentMentionName ? [{ name: params.agentMentionName }] : undefined, variant: params.variant, files: params.files, additionalParts: params.additionalParts, @@ -785,6 +787,7 @@ export const useSessionUIStore = create()((set, get) => ({ providerID, modelID, agent: effectiveDraftAgent, + agentMentionName, variant, inputMode, files, @@ -860,6 +863,7 @@ export const useSessionUIStore = create()((set, get) => ({ providerID, modelID, agent: effectiveAgent, + agentMentionName, variant, inputMode, files,