chore(integration): snapshot local main worktree

This commit is contained in:
inman
2026-08-31 10:23:21 +08:00
parent 48a9189939
commit 33fb31fb28
116 changed files with 8108 additions and 3026 deletions

View File

@@ -690,6 +690,38 @@ describe('coding Conversation store', () => {
await selection;
});
it('refreshes an existing Conversation silently without surfacing a reconnect state', async () => {
const refresh = deferred<ConversationSnapshot>();
const store = createCodingConversationStore({
getSnapshot: vi.fn(() => refresh.promise),
openEvents: vi.fn(),
submitPrompt: vi.fn(),
createId: ids(),
});
store.getState().applySnapshotEvent(snapshotEvent(snapshot('conversation-a', 1)));
const flight = store.getState().loadSnapshot('conversation-a', 'silent');
expect(store.getState().entriesByConversationId['conversation-a']).toMatchObject({
loadState: 'live',
error: null,
});
const refreshed = snapshot('conversation-a', 1, 1);
refreshed.conversation.model = {
model: { accountId: 'account-a', modelId: 'model-b', thinkingLevel: 'low' },
modelResolution: 'resolved',
};
refresh.resolve(refreshed);
await flight;
expect(store.getState().entriesByConversationId['conversation-a']).toMatchObject({
loadState: 'live',
error: null,
});
expect(selectCodingConversationSnapshot('conversation-a')(store.getState())?.conversation.model)
.toEqual(refreshed.conversation.model);
});
it('surfaces a cold preparation deadline and recovers without losing selection or draft', async () => {
const getSnapshot = vi.fn()
.mockImplementationOnce(() => new Promise<ConversationSnapshot>(() => undefined))
@@ -926,6 +958,51 @@ describe('coding Conversation store', () => {
});
});
it('ignores a snapshot-covered batch and applies only an overlapping unseen tail', () => {
const getSnapshot = vi.fn(() => Promise.resolve(snapshot('conversation-a')));
const store = createCodingConversationStore({
getSnapshot,
openEvents: vi.fn(),
submitPrompt: vi.fn(),
createId: ids(),
});
store.getState().applySnapshotEvent(snapshotEvent({
...snapshot('conversation-a', 1, 3),
run: { status: 'running', runId: 'run-a' },
}));
store.getState().applyPatchBatchEvent(patchBatch('conversation-a', 2, [
{ op: 'run.state', run: { status: 'running', runId: 'run-a' } },
{
op: 'context.replace',
context: { usedTokens: 10, contextWindow: 100, compaction: 'idle' },
},
]));
expect(getSnapshot).not.toHaveBeenCalled();
expect(selectCodingConversationSnapshot('conversation-a')(store.getState()))
.toMatchObject({ cursor: { seq: 3 }, context: { usedTokens: 0 } });
store.getState().applyPatchBatchEvent(patchBatch('conversation-a', 3, [
{
op: 'context.replace',
context: { usedTokens: 10, contextWindow: 100, compaction: 'idle' },
},
{
op: 'run.state',
run: { status: 'idle', runId: 'run-a', terminalReason: 'completed' },
},
]));
expect(getSnapshot).not.toHaveBeenCalled();
expect(selectCodingConversationSnapshot('conversation-a')(store.getState()))
.toMatchObject({
cursor: { workerGeneration: 1, seq: 4 },
context: { usedTokens: 0 },
run: { status: 'idle', runId: 'run-a', terminalReason: 'completed' },
});
expect(store.getState().entriesByConversationId['conversation-a'].loadState).toBe('live');
});
it('refreshes a target Snapshot once more when the first recovery response is still behind', async () => {
const firstRecovery = deferred<ConversationSnapshot>();
const secondRecovery = deferred<ConversationSnapshot>();