chore(integration): snapshot local main worktree
This commit is contained in:
@@ -36,6 +36,8 @@ export type CodingConversationLoadState =
|
||||
| 'recovering'
|
||||
| 'error';
|
||||
|
||||
export type CodingConversationSnapshotLoadMode = 'loading' | 'recovering' | 'silent';
|
||||
|
||||
export interface CodingConversationEntry {
|
||||
reducer: ConversationReducerState;
|
||||
loadState: CodingConversationLoadState;
|
||||
@@ -72,7 +74,10 @@ export interface CodingConversationStoreState {
|
||||
primeConversation(snapshot: ConversationSnapshot): void;
|
||||
clearConversationSelection(): void;
|
||||
selectConversation(conversationId: string): Promise<void>;
|
||||
loadSnapshot(conversationId: string, recovering?: boolean): Promise<ConversationSnapshot>;
|
||||
loadSnapshot(
|
||||
conversationId: string,
|
||||
mode?: CodingConversationSnapshotLoadMode,
|
||||
): Promise<ConversationSnapshot>;
|
||||
recoverConversation(conversationId: string): Promise<void>;
|
||||
connectEvents(): Promise<void>;
|
||||
disconnectEvents(): void;
|
||||
@@ -434,7 +439,7 @@ export function createCodingConversationStore(
|
||||
recoveryRefreshKeys.set(conversationId, refreshKey);
|
||||
queueMicrotask(() => {
|
||||
if (!snapshotLoads.has(conversationId)) {
|
||||
void store.getState().loadSnapshot(conversationId, true).catch(() => undefined);
|
||||
void store.getState().loadSnapshot(conversationId, 'recovering').catch(() => undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -509,27 +514,32 @@ export function createCodingConversationStore(
|
||||
const snapshotFlight = !entry?.reducer.snapshot
|
||||
|| entry.reducer.invalidation
|
||||
|| entry.loadState !== 'live'
|
||||
? get().loadSnapshot(conversationId, Boolean(entry?.reducer.invalidation))
|
||||
? get().loadSnapshot(
|
||||
conversationId,
|
||||
entry?.reducer.invalidation ? 'recovering' : 'loading',
|
||||
)
|
||||
: Promise.resolve(entry.reducer.snapshot);
|
||||
await Promise.all([snapshotFlight, get().connectEvents()]);
|
||||
},
|
||||
|
||||
loadSnapshot(conversationId, recovering = false) {
|
||||
loadSnapshot(conversationId, mode = 'loading') {
|
||||
const prior = snapshotLoads.get(conversationId);
|
||||
if (prior) return prior;
|
||||
set((state) => {
|
||||
const entry = state.entriesByConversationId[conversationId] ?? emptyEntry();
|
||||
return {
|
||||
entriesByConversationId: {
|
||||
...state.entriesByConversationId,
|
||||
[conversationId]: {
|
||||
...entry,
|
||||
loadState: recovering ? 'recovering' : 'loading',
|
||||
error: null,
|
||||
if (mode !== 'silent') {
|
||||
set((state) => {
|
||||
const entry = state.entriesByConversationId[conversationId] ?? emptyEntry();
|
||||
return {
|
||||
entriesByConversationId: {
|
||||
...state.entriesByConversationId,
|
||||
[conversationId]: {
|
||||
...entry,
|
||||
loadState: mode,
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
const flight = withPreparationDeadline(
|
||||
deps.getSnapshot(conversationId),
|
||||
deps.preparationTimeoutMs,
|
||||
@@ -545,16 +555,18 @@ export function createCodingConversationStore(
|
||||
return snapshot;
|
||||
})
|
||||
.catch((error) => {
|
||||
const failure = errorDetails(error);
|
||||
set((state) => {
|
||||
const entry = state.entriesByConversationId[conversationId] ?? emptyEntry();
|
||||
return {
|
||||
entriesByConversationId: {
|
||||
...state.entriesByConversationId,
|
||||
[conversationId]: { ...entry, loadState: 'error', error: failure.message },
|
||||
},
|
||||
};
|
||||
});
|
||||
if (mode !== 'silent') {
|
||||
const failure = errorDetails(error);
|
||||
set((state) => {
|
||||
const entry = state.entriesByConversationId[conversationId] ?? emptyEntry();
|
||||
return {
|
||||
entriesByConversationId: {
|
||||
...state.entriesByConversationId,
|
||||
[conversationId]: { ...entry, loadState: 'error', error: failure.message },
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -579,7 +591,7 @@ export function createCodingConversationStore(
|
||||
});
|
||||
try {
|
||||
await deps.recover(conversationId);
|
||||
await get().loadSnapshot(conversationId, true);
|
||||
await get().loadSnapshot(conversationId, 'recovering');
|
||||
} catch (error) {
|
||||
const failure = errorDetails(error);
|
||||
set((state) => {
|
||||
@@ -689,7 +701,10 @@ export function createCodingConversationStore(
|
||||
async submitPrompt(input) {
|
||||
let entry = get().entriesByConversationId[input.conversationId];
|
||||
if (!entry?.reducer.snapshot || entry.reducer.invalidation) {
|
||||
await get().loadSnapshot(input.conversationId, Boolean(entry?.reducer.invalidation));
|
||||
await get().loadSnapshot(
|
||||
input.conversationId,
|
||||
entry?.reducer.invalidation ? 'recovering' : 'loading',
|
||||
);
|
||||
entry = get().entriesByConversationId[input.conversationId];
|
||||
}
|
||||
if (!entry?.reducer.snapshot) throw new Error('Conversation snapshot is unavailable');
|
||||
@@ -915,30 +930,43 @@ export function createCodingConversationStore(
|
||||
|| Boolean(currentBeforeBatch?.reducer.invalidation);
|
||||
if (!validRange) {
|
||||
if (!snapshotLoads.has(event.conversationId)) {
|
||||
void get().loadSnapshot(event.conversationId, true).catch(() => undefined);
|
||||
void get().loadSnapshot(event.conversationId, 'recovering').catch(() => undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let applicableEvent = event;
|
||||
if (currentSnapshot
|
||||
&& event.workerGeneration === currentSnapshot.cursor.workerGeneration
|
||||
&& event.fromSeq <= currentSnapshot.cursor.seq) {
|
||||
const unseenItems = event.items.filter((item) => item.seq > currentSnapshot.cursor.seq);
|
||||
if (unseenItems.length === 0) return;
|
||||
applicableEvent = {
|
||||
...event,
|
||||
fromSeq: unseenItems[0]!.seq,
|
||||
toSeq: unseenItems.at(-1)!.seq,
|
||||
items: unseenItems,
|
||||
};
|
||||
}
|
||||
if (recovering
|
||||
|| !currentSnapshot
|
||||
|| event.workerGeneration !== currentSnapshot.cursor.workerGeneration
|
||||
|| event.fromSeq !== currentSnapshot.cursor.seq + 1) {
|
||||
bufferRecoveryBatch(event);
|
||||
if (!snapshotLoads.has(event.conversationId)) {
|
||||
void get().loadSnapshot(event.conversationId, true).catch(() => undefined);
|
||||
|| applicableEvent.workerGeneration !== currentSnapshot.cursor.workerGeneration
|
||||
|| applicableEvent.fromSeq !== currentSnapshot.cursor.seq + 1) {
|
||||
bufferRecoveryBatch(applicableEvent);
|
||||
if (!snapshotLoads.has(applicableEvent.conversationId)) {
|
||||
void get().loadSnapshot(applicableEvent.conversationId, 'recovering').catch(() => undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const shouldSyncSettledSession = completesOrdinaryPrompt(event, currentSnapshot);
|
||||
const shouldSyncSettledSession = completesOrdinaryPrompt(applicableEvent, currentSnapshot);
|
||||
let recover = false;
|
||||
let applied = false;
|
||||
set((state) => {
|
||||
const current = state.entriesByConversationId[event.conversationId] ?? emptyEntry();
|
||||
const current = state.entriesByConversationId[applicableEvent.conversationId] ?? emptyEntry();
|
||||
let reducer = current.reducer;
|
||||
for (const item of event.items) {
|
||||
for (const item of applicableEvent.items) {
|
||||
reducer = reduceConversationPatch(reducer, {
|
||||
conversationId: event.conversationId,
|
||||
workerGeneration: event.workerGeneration,
|
||||
conversationId: applicableEvent.conversationId,
|
||||
workerGeneration: applicableEvent.workerGeneration,
|
||||
...(item.runId ? { runId: item.runId } : {}),
|
||||
seq: item.seq,
|
||||
at: item.at,
|
||||
@@ -951,10 +979,10 @@ export function createCodingConversationStore(
|
||||
}
|
||||
if (reducer === current.reducer) return state;
|
||||
applied = true;
|
||||
const incomingMessages = event.items.flatMap((item) => (
|
||||
const incomingMessages = applicableEvent.items.flatMap((item) => (
|
||||
item.patch.op === 'message.upsert' ? [item.patch.node] : []
|
||||
));
|
||||
const unread = state.selectedConversationId !== event.conversationId
|
||||
const unread = state.selectedConversationId !== applicableEvent.conversationId
|
||||
&& incomingMessages.some((message) => message.role === 'assistant')
|
||||
? true
|
||||
: current.unread;
|
||||
@@ -965,7 +993,7 @@ export function createCodingConversationStore(
|
||||
error: null,
|
||||
unread,
|
||||
};
|
||||
const requests = state.requestsByConversationId[event.conversationId] ?? {};
|
||||
const requests = state.requestsByConversationId[applicableEvent.conversationId] ?? {};
|
||||
const nextRequests = reducer.snapshot
|
||||
? withoutReconciledRequests(requests, reducer.snapshot)
|
||||
: requests;
|
||||
@@ -973,21 +1001,21 @@ export function createCodingConversationStore(
|
||||
return {
|
||||
entriesByConversationId: {
|
||||
...state.entriesByConversationId,
|
||||
[event.conversationId]: entry,
|
||||
[applicableEvent.conversationId]: entry,
|
||||
},
|
||||
summariesByConversationId: summariesWithEntry(state.summariesByConversationId, entry),
|
||||
requestsByConversationId: !requestsChanged
|
||||
? state.requestsByConversationId
|
||||
: {
|
||||
...state.requestsByConversationId,
|
||||
[event.conversationId]: nextRequests,
|
||||
[applicableEvent.conversationId]: nextRequests,
|
||||
},
|
||||
};
|
||||
});
|
||||
if (recover) {
|
||||
void get().loadSnapshot(event.conversationId, true).catch(() => undefined);
|
||||
void get().loadSnapshot(applicableEvent.conversationId, 'recovering').catch(() => undefined);
|
||||
} else if (applied && shouldSyncSettledSession) {
|
||||
const settledSnapshot = selectCodingConversationSnapshot(event.conversationId)(get());
|
||||
const settledSnapshot = selectCodingConversationSnapshot(applicableEvent.conversationId)(get());
|
||||
if (settledSnapshot) deps.queueSettledSessionSync(settledSnapshot);
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user