Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function harness(options: {
hasDurableMessage: (id) => messages.some((message) => message.id === id),
},
ready: async () => {}, waitForDurableMessage: async () => true,
reload: async () => {}, loadEarlier: async () => {}, observationChanged: () => {},
reload: async () => {}, holdsCachedTranscript: () => false, loadEarlier: async () => {}, observationChanged: () => {},
close: async () => {},
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe('Conversation owner context compaction', () => {
hasDurableMessage: () => false,
},
ready: async () => {}, waitForDurableMessage: async () => true,
reload: async () => {}, loadEarlier: async () => {}, observationChanged: () => {},
reload: async () => {}, holdsCachedTranscript: () => false, loadEarlier: async () => {}, observationChanged: () => {},
close: async () => {},
});
services.observation.subscribeEvents = (_sessionId, onEvent, phase) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/__tests__/conversation-owner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function harness(options: {
hasDurableMessage: (id) => messages.some((message) => message.id === id),
},
ready: async () => {}, waitForDurableMessage: async () => true,
reload: async () => {}, loadEarlier: async () => {}, observationChanged: () => {},
reload: async () => {}, holdsCachedTranscript: () => false, loadEarlier: async () => {}, observationChanged: () => {},
close: async () => { resource.closed = true; },
};
};
Expand Down
303 changes: 303 additions & 0 deletions apps/desktop/src/main/__tests__/conversation-transcript-retry.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function useConversationObservation(options: {
ConversationTranscriptController
>;
transcriptRangeRef: RefBox<ConversationTranscriptController | undefined>;
observationRef: import('../model/conversation-workspace.js').ConversationWorkspace['observationRef'];
setSessionEventHealthBySession: SessionEventHealthUpdater;
toastApi: Pick<ToastApi, 'error'>;
}) {
Expand Down Expand Up @@ -125,6 +126,8 @@ export function useConversationObservation(options: {
const controller = options.services.openTranscript(activeId, (error) => {
if (!disposed) applyReadError(activeId, error);
});
const observation = { sessionId: activeId, controller };
options.observationRef.current = observation;
const transcript = controller.store;
const unsubscribeTranscript = transcript.subscribe(() =>
applyTranscript(activeId, controller, () => !disposed));
Expand Down Expand Up @@ -175,6 +178,7 @@ export function useConversationObservation(options: {
subscribeSessionEvents();
return () => {
disposed = true;
if (options.observationRef.current === observation) options.observationRef.current = undefined;
options.endObservation(activeId);
observationAttempt += 1;
if (observationRetryTimer !== undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export function createConversationWorkspace(catalog: SessionCatalogController, s
const activeIdRef = { current: undefined as string | undefined };
const messagesRef = { current: [] as StoredMessage[] };
const transcriptRangeRef = { current: undefined as ConversationTranscriptController | undefined };
const observationRef = { current: undefined as {
readonly sessionId: string;
readonly controller: ConversationTranscriptController;
} | undefined };
const transientMessagesBySessionRef = { current: new Map<string, Map<string, TransientUserMessageProjection>>() };
const selectionRevisionRef = { current: 0 };
let seed = INITIAL_LIVE_CONTENT_SEED;
Expand Down Expand Up @@ -121,7 +125,7 @@ export function createConversationWorkspace(catalog: SessionCatalogController, s
});
if (hasNewTaskReloadIntent()) bootstrapSelectionLease.release();
return {
ui, activeIdRef, transcriptRangeRef, bootstrapSelectionLease, commands,
ui, activeIdRef, transcriptRangeRef, observationRef, bootstrapSelectionLease, commands,
publishedSession: Object.freeze({ get current() { return activeIdRef.current; } }),
messages: reader((value) => value.messages),
usage: (model: string | undefined, connectionId: string | undefined) => reader((value) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type { UiLocale } from '@maka/core/ui-locale';
import type { ConversationWorkspace } from './conversation-workspace.js';
import { transcriptErrorMessage, transcriptRefreshTitle } from '../../../application/contracts/transcript-copy.js';
export function createTranscriptCommands(workspace: ConversationWorkspace, feedback: { current: { locale: UiLocale; toast: Pick<ReturnType<typeof useToast>, 'error'> } }) {
const { ui, activeIdRef, transcriptRangeRef } = workspace;
const { ui, activeIdRef, transcriptRangeRef, observationRef } = workspace;
const reportError = (sessionId: string, error: unknown) => {
const { locale, toast } = feedback.current;
const message = transcriptErrorMessage(error, locale, 'refresh');
Expand Down Expand Up @@ -51,10 +51,16 @@ export function createTranscriptCommands(workspace: ConversationWorkspace, feedb
}
},
async retryMessages(sessionId: string) {
if (activeIdRef.current !== sessionId || !ui.messageRetryPending.claim(sessionId)) return;
const controller = transcriptRangeRef.current;
try { await controller?.reload(); }
catch (error) { if (activeIdRef.current === sessionId && transcriptRangeRef.current === controller) reportError(sessionId, error); }
const observation = observationRef.current;
if (!observation || observation.sessionId !== sessionId || !workspace.commands.isSessionSelected(sessionId)
|| !ui.messageRetryPending.claim(sessionId)) return;
// The controller reports a failed reload through the observation's onError, as for its own
// reloads, except over the cached transcript, where only this Retry can answer the click.
try { await observation.controller.reload(); }
catch (error) {
if (observation.controller.holdsCachedTranscript() && observationRef.current === observation
&& workspace.commands.isSessionSelected(sessionId)) reportError(sessionId, error);
}
finally { ui.messageRetryPending.release(sessionId); }
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface ConversationTranscriptController {
waitForDurableMessage(messageId: string, timeoutMs: number): Promise<boolean>;
loadEarlier(throughSequence?: number): Promise<void>;
reload(): Promise<void>;
holdsCachedTranscript(): boolean;
observationChanged(phase: 'pending' | 'ready'): void;
close(): Promise<void>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function ConversationLifecycle(props: {
services: services.observation, uiLocale, toastApi,
activeId: requested?.localState !== 'pending' ? requested?.id : undefined,
observationAuthorityRevision: authority.current.generation,
activeIdRef, transcriptRangeRef,
activeIdRef, transcriptRangeRef, observationRef: workspace.observationRef,
handleEvent: handlers.handleEvent,
publishTranscript: workspace.publishTranscript,
commitTranscript: workspace.commitTranscript,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export interface DesktopTranscriptRangeController {
/** One budget of earlier history, or everything down to `throughSequence` in one answer. */
loadEarlier(throughSequence?: number): Promise<void>;
reload(): Promise<void>;
/** Whether the range holds the cached transcript, over which a failed reload is not reported. */
holdsCachedTranscript(): boolean;
observationChanged(phase: 'pending' | 'ready'): void;
close(): Promise<void>;
}
Expand Down Expand Up @@ -246,6 +248,7 @@ export function createDesktopTranscriptRangeController(
},
loadEarlier,
reload,
holdsCachedTranscript: cached,
observationChanged: recovery.observationChanged,
async close() {
if (closed) return;
Expand Down