fix(mobile): recover pending questions after cold start
This commit is contained in:
@@ -1178,23 +1178,24 @@ const updateRoutingIndexFromEvent = (
|
||||
* recovery paths only; normal session switches rely on primary SSE reducer
|
||||
* state for `question.asked` / `permission.asked` events. When
|
||||
* `candidateSessionIds` is omitted, every session known to the directory store
|
||||
* is treated as a candidate.
|
||||
* is treated as a candidate; when provided, recovery is limited to those IDs.
|
||||
*/
|
||||
export async function resyncBlockingRequestsForDirectory(
|
||||
directory: string,
|
||||
store: StoreApi<DirectoryStore>,
|
||||
candidateSessionIds?: string[],
|
||||
options?: { includePermissions?: boolean },
|
||||
) {
|
||||
const before = store.getState()
|
||||
const knownSessionIds = new Set<string>([
|
||||
const candidateIds = new Set<string>(candidateSessionIds ?? [
|
||||
...before.session.map((session) => session.id),
|
||||
...Object.keys(before.message ?? {}),
|
||||
...Object.keys(before.session_status ?? {}),
|
||||
...Object.keys(before.question ?? {}),
|
||||
...Object.keys(before.permission ?? {}),
|
||||
])
|
||||
const candidates = candidateSessionIds ?? Array.from(knownSessionIds)
|
||||
if (candidates.length === 0) return
|
||||
if (candidateIds.size === 0) return
|
||||
const candidates = Array.from(candidateIds)
|
||||
|
||||
// Re-fetch pending questions that may have been asked during an SSE gap,
|
||||
// reconnect window, or directory materialization gap.
|
||||
@@ -1204,12 +1205,12 @@ export async function resyncBlockingRequestsForDirectory(
|
||||
)
|
||||
const pendingQuestions = await opencodeClient.listPendingQuestions({ directories: [directory] })
|
||||
const grouped: Record<string, QuestionRequest[]> = {}
|
||||
for (const q of pendingQuestions) {
|
||||
if (!q?.id || !q.sessionID) continue
|
||||
if (!knownSessionIds.has(q.sessionID)) continue
|
||||
const list = grouped[q.sessionID]
|
||||
if (list) list.push(q)
|
||||
else grouped[q.sessionID] = [q]
|
||||
for (const question of pendingQuestions) {
|
||||
if (!question?.id || !question.sessionID) continue
|
||||
if (!candidateIds.has(question.sessionID)) continue
|
||||
const list = grouped[question.sessionID]
|
||||
if (list) list.push(question)
|
||||
else grouped[question.sessionID] = [question]
|
||||
}
|
||||
for (const sessionId of Object.keys(grouped)) {
|
||||
grouped[sessionId].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
||||
@@ -1256,6 +1257,8 @@ export async function resyncBlockingRequestsForDirectory(
|
||||
// Non-fatal: question resync best-effort
|
||||
}
|
||||
|
||||
if (options?.includePermissions === false) return
|
||||
|
||||
// Re-fetch pending permissions — same rationale as questions.
|
||||
try {
|
||||
const beforeSignatures = new Map(
|
||||
@@ -1265,7 +1268,7 @@ export async function resyncBlockingRequestsForDirectory(
|
||||
const grouped: Record<string, PermissionRequest[]> = {}
|
||||
for (const permission of pendingPermissions) {
|
||||
if (!permission?.id || !permission.sessionID) continue
|
||||
if (!knownSessionIds.has(permission.sessionID)) continue
|
||||
if (!candidateIds.has(permission.sessionID)) continue
|
||||
const list = grouped[permission.sessionID]
|
||||
if (list) list.push(permission)
|
||||
else grouped[permission.sessionID] = [permission]
|
||||
@@ -1330,6 +1333,15 @@ export async function resyncBlockingRequestsForDirectory(
|
||||
}
|
||||
}
|
||||
|
||||
export async function resyncBlockingRequestsForActiveDirectory(
|
||||
directory: string,
|
||||
childStores: ChildStoreManager,
|
||||
) {
|
||||
const store = childStores.getChild(directory)
|
||||
if (!store) return
|
||||
await resyncBlockingRequestsForDirectory(directory, store)
|
||||
}
|
||||
|
||||
async function resyncDirectoryAfterReconnect(
|
||||
directory: string,
|
||||
store: StoreApi<DirectoryStore>,
|
||||
@@ -1928,6 +1940,7 @@ export function SyncProvider(props: {
|
||||
const lastFullResyncAtByDirectoryRef = useRef(new Map<string, number>())
|
||||
const lastChildDiscoveryAtByDirectoryRef = useRef(new Map<string, number>())
|
||||
const resyncingDirectoriesRef = useRef(new Set<string>())
|
||||
const blockingRequestResyncingDirectoriesRef = useRef(new Set<string>())
|
||||
const statusPollingDirectoriesRef = useRef(new Set<string>())
|
||||
const pipelineReconnectRef = useRef<((reason?: string) => void) | null>(null)
|
||||
const pipelineHasConnectedRef = useRef(false)
|
||||
@@ -1961,6 +1974,24 @@ export function SyncProvider(props: {
|
||||
})
|
||||
}, [childStores, routingIndex])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
const onSystemResume = () => {
|
||||
const directory = currentDirectoryRef.current
|
||||
if (!directory || !childStores.getChild(directory)) return
|
||||
|
||||
const resyncing = blockingRequestResyncingDirectoriesRef.current
|
||||
if (resyncing.has(directory)) return
|
||||
resyncing.add(directory)
|
||||
void resyncBlockingRequestsForActiveDirectory(directory, childStores)
|
||||
.finally(() => resyncing.delete(directory))
|
||||
}
|
||||
|
||||
window.addEventListener("openchamber:system-resume", onSystemResume)
|
||||
return () => window.removeEventListener("openchamber:system-resume", onSystemResume)
|
||||
}, [childStores])
|
||||
|
||||
// Configure child store manager
|
||||
useEffect(() => {
|
||||
void usePermissionStore.getState().hydrate().catch(() => undefined)
|
||||
|
||||
Reference in New Issue
Block a user